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

Postmark Bundle Laravel Package

stephenfarrell/postmark-bundle

A simple integration for sending transactional email through Postmark in PHP applications, with configurable service wiring for Postmark’s API. Useful for adding reliable email delivery to Symfony or Laravel-style projects with minimal setup.

View on GitHub
Deep Wiki
Context7

Getting Started

The first thing to understand: this is a Symfony 3 bundle, and Laravel does not load Symfony bundles. There is no config/bundles.php, no Extension registration, and no services.yaml auto-wiring in a Laravel app. You cannot composer require stephenfarrell/postmark-bundle and expect it to "just work."

So "getting started" means deciding why you looked at this package and picking the right layer:

  1. If you want Postmark in Laravel, skip this package entirely. Laravel's mailer already speaks Postmark over SMTP (MAIL_MAILER=smtp, MAIL_HOST=smtp.postmarkapp.com, username = server token, password = server token). For the HTTP API (templates, batch, inbound, message streams), use wildbit/postmark-php directly.
  2. If you want to salvage the bundle's ideas, open composer.json first. It pins an older Postmark HTTP client and Symfony 3 DI components. Note the exact wildbit/postmark-php (or equivalent) version — that's the only piece you'll actually reuse.
  3. First practical use case: sending a transactional email (password reset, receipt, invite) through a Postmark server token stored in .env.

A minimal Laravel-native replacement for what the bundle does:

composer require wildbit/postmark-php
// config/services.php
'postmark' => [
    'token' => env('POSTMARK_TOKEN'),
    'message_stream' => env('POSTMARK_MESSAGE_STREAM', 'outbound'),
],

Bind a singleton in AppServiceProvider::register():

$this->app->singleton(\Postmark\PostmarkClient::class, fn () => new \Postmark\PostmarkClient(
    config('services.postmark.token')
));

First send:

$client = app(\Postmark\PostmarkClient::class);
$client->sendEmail(
    from: '[email protected]',
    to: $user->email,
    subject: 'Welcome',
    htmlBody: view('emails.welcome', compact('user'))->render(),
    messageStream: config('services.postmark.message_stream'),
);

That is functionally what this bundle gives a Symfony 3 app — nothing more.

Implementation Patterns

Once you accept the bundle itself is inert in Laravel, the useful patterns are the ones it intended to provide, reimplemented as Laravel idioms.

Service provider + singleton. The bundle registers a postmark DI service keyed off config; do the same via AppServiceProvider. Inject PostmarkClient anywhere (controllers, jobs, notifications) rather than resolving from the container inline.

Facade optional. If your team prefers Postmark::sendEmail(...), add a thin facade over the singleton. Keep the real client injectable underneath so it stays testable.

Queued sending. Postmark is an external HTTP call — never send inline in a request path. Wrap sends in a ShouldQueue job or use Laravel's Mail::to()->queue() with a Postmark-backed transport. This is the single biggest day-to-day win:

Mail::to($user)->queue(new WelcomeMail($user));

Transactional vs. broadcast separation. The bundle's config exposes a server token; in practice you need two tokens (transactional stream vs. broadcast stream) plus a message stream name. Model this explicitly in config/services.php and pick the stream per mail class rather than hardcoding it.

Templates. Use Postmark's server-side templates for HTML the marketing team edits, and Laravel Blade for developer-owned mail. Route template sends through sendEmailWithTemplate() and Blade sends through sendEmail(). Don't mix them within one mail class.

Webhooks (bounces, opens, inbound). Postmark POSTs to your app. Register routes in routes/api.php, validate the Postmark signature header, and dispatch to jobs. The bundle has no meaningful webhook support — build this fresh.

Testing. Bind a fake PostmarkClient (or use Mail::fake()) in tests. Never hit the real API in CI.

Gotchas and Tips

  • Symfony 3 is EOL (since Nov 2018). Any transitive dependency the bundle pulls in will be old, possibly flagged by composer audit. Do not install this into a production Laravel app; cherry-pick the ideas instead.
  • Zero stars, zero dependents. There is no community, no issue history to learn from, and no one has vetted this against current Postmark API changes. Treat it as a reference implementation, not a dependency.
  • Namespace/container mismatch. Symfony ContainerInterface, Bundle classes, and compiler passes have no Laravel equivalent. Copying the bundle's service definitions will not work; rewrite them as AppServiceProvider bindings.
  • Config key drift. The bundle's parameter names will not match Laravel's config('services.*') conventions. Don't try to mirror them — adopt Laravel conventions so future developers can find the config.
  • Token vs. server-vs-account token confusion. Postmark has account tokens and server tokens; sending mail requires the server token. A misconfigured token returns a 401 that looks like an auth bug but is actually a wrong-token-type bug. Log the Postmark ErrorCode and Message verbatim.
  • Message stream requirement. Since Postmark's 2021 change, omitting MessageStream in API calls causes 422s. If you port any old send code, add the stream parameter.
  • Debugging loop. Postmark's dashboard Activity tab shows every send, its API response, and bounce reason — use it before adding logging. The /message-streams and /server API endpoints confirm your token and stream config.
  • Extension points. The real extensibility lives in Laravel, not the bundle: a PostmarkClient binding (swap for a mock in tests), a Mail transport if you prefer the mailer abstraction, and queued jobs for retries. If you need custom headers or metadata, wildbit/postmark-php exposes them directly — no bundle wrapper needed.
  • When to reconsider. If you're on a legacy Symfony 3 app that already uses this bundle, pin it, freeze the lockfile, and plan migration. If you're on Laravel, there is no scenario where this package is the right choice over wildbit/postmark-php + a service provider.
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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons