uam/postmark-swiftmailer-bundle
A Symfony bundle that plugs Postmark into Swiftmailer, adding a mail transport so you can send transactional email through Postmark's API using standard Symfony service configuration. Keeps Swiftmailer's spooling, events, and existing mailer workflow intact.
Architecture fit — poor, and the premise needs correcting. Despite the framing, this is not a Laravel package. It is a Symfony2 bundle: it ships a Bundle subclass, a DI extension, and XML/YAML service definitions that hook into Symfony's swiftmailer.transport service graph. Laravel has no bundle registry, no AppKernel, no compiler passes, and no service-tag semantics, so composer require would install code that never executes. The genuinely reusable artifact is a single class implementing Swift_Transport (start/stop/ping/send) that POSTs to Postmark's /email endpoint. That is roughly 100–200 lines of HTTP glue, and it is the only part worth extracting.
The package's reason to exist has expired. SwiftMailer reached end-of-life in November 2021 and was superseded by Symfony Mailer, which now ships a first-party Postmark bridge (symfony/postmark-mailer). Laravel 9+ moved to Symfony Mailer internally, and Laravel already supports Postmark with zero code via SMTP credentials. A SwiftMailer-only Postmark transport is a solution to a problem that no longer exists on any supported stack.
Integration feasibility — low as distributed. Symfony2-era composer.json constraints (symfony/symfony ~2.x, PHP ~5.3–5.6) will not resolve against any current PHP 8.x application; dependency resolution fails before code ever loads. Even on a legacy Laravel 5.x–8.x app that still uses SwiftMailer under the hood, Laravel binds its own transport factory and does not read Symfony bundle configuration, so registration is manual regardless.
Technical risk — high, concentrated in the untested edges. Signals: 2 stars, 0 dependents, no CI badge, no releases, no security policy, no maintainer activity for roughly a decade. The historical HTTP client is likely cURL with no timeout policy, no retry/backoff, no idempotency key, and no mapping of Postmark's structured error codes (401/422/429/503) into transport exceptions. It predates Postmark message streams, so the Message-Stream header — required for transactional vs. broadcast separation on modern accounts — is almost certainly missing. Bounce, spam-complaint, and inbound handling are out of scope entirely; those need a separate webhook integration.
Key questions to close before any commitment:
Verdict: adopt nothing. Treat the repository as a reference implementation, budget half a day to read and re-implement the transport against the modern Postmark API, and do not add it to the dependency graph.
Stack fit. Three viable paths, in descending order of preference:
| Option | Fit | Effort | When to use |
|---|---|---|---|
Postmark SMTP via config/mail.php |
Exact | ~30 min | Default. Works on every Laravel version, no code, vendor-supported. |
symfony/postmark-mailer bridge (Laravel 9+) or a maintained Laravel Postmark transport package |
Exact | ~1 day | Need API-specific features: message streams, templates, batch, structured errors. |
| Extract and vendor this SwiftMailer transport class | Partial | ~3–5 days incl. tests | Only if a legacy Laravel ≤ 8 app must stay on SwiftMailer short-term. |
Installing the bundle itself is not on the list — it is not an integration path in a Laravel codebase.
Migration path. (1) Inventory every outbound mail path: Mail::send, notifications, queued mailable jobs, error/alerting mailers, and any direct Swift_Mailer usage. (2) Stand up Postmark server tokens per environment with a sandbox/suppression-safe token for staging. (3) Cut over on SMTP first — it validates deliverability, DKIM/SPF, and the sending domain before any code changes. (4) Only then consider the API transport, and only if a concrete requirement (streams, templates, batch) justifies it. (5) Decommission SwiftMailer.
Compatibility. If extraction is unavoidable: pin the vendored class under an app namespace, wrap it behind Laravel's Mail::extend() or a transport binding so call sites stay untouched, and add an adapter that translates Postmark responses into Symfony\Component\Mailer\Exception-style exceptions. Verify PHP 8.x compatibility (typed properties, curl_* signatures, deprecated each()/dynamic properties), and write contract tests against Postmark's documented response shapes before touching production.
Sequencing. Deliver in four increments, each independently shippable: SMTP cutover → observability (send/bounce/complaint logging) → optional API transport → SwiftMailer removal. Do not bundle the transport rewrite with the provider migration; a deliverability incident during a code refactor is the worst of both worlds.
Maintenance — the dominant cost. Zero upstream activity means no bug fixes, no PHP 9 readiness, no Postmark API-version updates. The moment it enters the codebase, we own it permanently. Realistically: half a day of extraction, 2–3 days hardening (timeouts, retries, error mapping, tests), then a recurring line item for dependency review each PHP minor. That is far more expensive than SMTP, which requires no ownership at all.
Support. No vendor, no SLA, no maintainer to escalate to. Postmark support will not debug a third-party transport; incidents route to our on-call. Every future engineer who touches mail delivery inherits an unfamiliar, undocumented library — a genuine bus-factor risk given the package's near-zero public footprint (2 stars, 0 dependents means no Stack Overflow answers, no issue history to search).
Scaling. Synchronous HTTP per message with no connection reuse and no batch endpoint. Under load, each send blocks a PHP-FPM worker for a full round trip (~100–300 ms), which will dominate request latency. Mitigation: force all mail through queued jobs with worker concurrency tuned to Postmark's account rate limit, and implement 429/503 handling with exponential backoff and jitter. Note that a timeout after the request is accepted by Postmark causes a duplicate send on retry — there is no idempotency key — so retry policy must be conservative and deduplicated downstream.
Failure modes to instrument explicitly:
Message-Stream header causing transactional mail to be treated as broadcast (or rejected) on accounts with streams enforced.Ramp-up. Low conceptual complexity, high friction. A mid-level engineer needs roughly half a day to read the transport class and understand SwiftMailer's Swift_Transport contract, plus prior familiarity with Symfony DI to interpret the bundle wiring (which they will then discard). The real risk is not the learning curve but the hidden assumptions — API version, header handling, encoding — that are invisible until a specific message shape fails in production. Ramp-up should be treated as an investigation, not a code review.
Bottom line: the operational surface this package creates — permanent ownership, no upstream, unhandled failure modes, no feedback loop — is disproportionate to a problem already solved by a config file. Recommend SMTP now, maintained API transport if requirements demand it, and archive this package from consideration.
How can I help you explore Laravel packages today?