mlpz/postmark-bundle
mlpz/postmark-bundle provides Symfony integration for Postmark transactional email. It offers bundle configuration and services for sending messages through Postmark's API, making it straightforward to add reliable email delivery to Symfony applications.
First, the reality check: mlpz/postmark-bundle is a Symfony 2 bundle, not a Laravel package. It is archived, its last release was September 2013, and it has zero dependents. There is no ServiceProvider, no facade, no publishable config — Laravel's container cannot register a Symfony bundle, so you cannot "install and use" this in a Laravel app in any conventional sense.
What you can do day-to-day is treat it as a reference implementation and build the actual integration with supported tooling.
Minimal steps if you are evaluating it anyway:
DependencyInjection/ (the config tree and service wiring), Resources/config/services.*, and whatever sits under a Transport/ or Mailer/ directory. That is the entire surface area of the package.postmark: block in config.yml with an API/server token and optional public key). These map one-to-one onto what you would put in config/services.php and .env.composer why-not mlpz/postmark-bundle against your project before doing anything else. On PHP 8 / Symfony 6+ you will get a hard constraint failure because the package pins PHP 5.3-era and Symfony 2.x requirements.First use case: sending a single transactional email through Postmark's /email endpoint. In Laravel today that is:
composer require symfony/postmark-mailer
MAIL_MAILER=postmark
POSTMARK_TOKEN=your-server-token
and then a normal Mailable. Nothing in this bundle improves on that path.
1. Port, don't install. The useful artifact here is the shape of the Postmark payload the bundle builds: From, To, Cc, Bcc, Subject, HtmlBody, TextBody, Tag, TrackOpens, ReplyTo, Headers, Attachments. Copy that field list into a Laravel Mailable or a small DTO and you have reproduced the bundle's value in an hour.
2. Use Laravel's mailer, not a hand-rolled client. Laravel 9+ ships a first-party Postmark transport via symfony/postmark-mailer. Configure config/mail.php, keep credentials in config/services.php, and send through Mail::to(...)->send(...). This gives you queueing, events, Mail::fake() in tests, and local log/array drivers for free — none of which the bundle offers.
3. Reach for the SDK when you need more than mail. For inbound webhooks, templates, message streams, or batch sends, use wildbit/postmark-php (the official SDK) or plain Http::withHeaders(['X-Postmark-Server-Token' => ...])->post('https://api.postmarkapp.com/email', $payload). Wrap either behind an interface:
interface TransactionalMailer {
public function send(TransactionalMessage $message): string; // returns MessageID
}
Bind a Postmark implementation in a service provider and a FakeMailer in tests. This is the pattern the bundle was trying to provide for Symfony's container; you get a better version of it with Laravel's container for free.
4. Migrating off the bundle. If you are porting a legacy Symfony 2 app to Laravel, map $this->get('postmark.message')-style calls to an injected TransactionalMailer service, and convert Swiftmailer \Swift_Message construction into Mailable classes. Do the translation at the boundary (one adapter class) rather than rewriting every call site.
5. Keep the raw-API escape hatch. Some Postmark features (message streams, metadata, TrackLinks) are easier to hit directly than to thread through a mailer abstraction. A thin PostmarkClient wrapper around Http is the pragmatic middle ground.
require constraints, Symfony 2.x bundle structure, no composer.json compatibility with PHP 8. Do not spend time trying to force it; the failure is structural, not a version-pin tweak.X-Postmark-Message-Stream header or MessageStream field) and uses server tokens rather than the older account-level keys. Any payload copied verbatim from this bundle will get a 422 with an ErrorCode explaining exactly what is missing.{"ErrorCode": 300, "Message": "..."} with a 422. Whatever exception handling this bundle had is almost certainly a generic \Exception; reimplement with a typed exception that carries ErrorCode so you can distinguish "inactive recipient" (406) from "invalid API token" (401) in logs.MessageID header — log it. That ID is what Postmark's Activity feed and webhook payloads key on, and it is the fastest way to trace a "user says they didn't get the email" ticket.Mail::queue() or a queued job wrapping your TransactionalMailer.composer.json you inherited, run composer audit and plan removal.How can I help you explore Laravel packages today?