resolves a DSN into a first-party Symfony Mailer transport — which means Mautic, PrestaShop, Drupal, and any plain Symfony app all get it from one package, with honest errors instead of a generic SMTP rejection.
TL;DR: is a new package that turns into a valid Symfony Mailer DSN.
One , and every in a Symfony app — or in Mautic, PrestaShop, or anything else built on Symfony Mailer — delivers through MailKite's Send API instead of SMTP.
It's extracted from our existing package, which now depends on it rather than carrying its own copy of the mapping logic. 15 tests, no network, all green. will resolve once the package clears Packagist's one-time submission step — until then, install straight from the GitHub repo ( is tagged).
Why one package reaches three ecosystems Symfony Mailer resolves transports from a DSN string.
That's a small design choice with a large consequence: any platform built on Symfony Mailer inherits the same extension point, whether or not that platform has anything else to do with Symfony.
Write a implementation once, and it's live everywhere that mechanism is used: A plain Symfony app — set in , done.
Autoconfiguration picks up the factory the moment the package is installed.
Mautic 5+ — replaced SwiftMailer with Symfony Mailer and a DSN field in Configuration → Email Settings.
Mautic is worth calling out specifically: it's a marketing-automation platform, so email volume per install runs far ahead of a typical CMS or CRM contact form.
PrestaShop 8+ — Symfony-based mailer since PS8, same DSN mechanism.
Drupal, via the Symfony Mailer Lite module — a smaller install base (17,528 sites) but the same wiring.
We didn't build four integrations.
We built one, and the DSN did the rest.
What was actually new here — and what wasn't The transport itself isn't new code.
It's the same subclass that's lived inside since that package shipped: converts a into a MailKite payload via , calls the API, and stamps the returned message ID back onto the message.
What's new is the factory — the piece that resolves a DSN string into that transport — and moving the transport itself out from under Laravel so it isn't Laravel's to own: 's now requires and its instantiates directly — the ~200 lines of address, body, and attachment mapping logic exist in exactly one place.
The Laravel package's own test suite dropped from 14 tests to 4, because it's no longer testing logic it doesn't own; it tests wiring (does resolve to the right transport, does the SDK client singleton bind, does a missing key throw a helpful error).
Honest failures instead of a generic SMTP rejection This is the part worth dwelling on if you're currently pointing Mautic, PrestaShop, or Drupal at generic SMTP.
SMTP gives you a numeric code and maybe a line of server-specific text.
The MailKite API returns its own error message — "domain not verified," a suppressed recipient, a rate limit — and this transport surfaces it verbatim instead of translating it into an SMTP-shaped abstraction that loses the detail: The same honesty applies to what the transport won't silently drop: Situation What happens Custom header (, a tag/metadata header) Throws — the API's envelope has no room for it, so refusing beats a header that quietly never arrives Inline (-embedded) image Throws — the API has no Content-ID support; host the image at a URL instead More than one reply-to address Throws — the API accepts exactly one Unverified domain Throws with the API's own message and HTTP status Every one of those is a case where a generic SMTP transport would either accept the message and let it fail downstream, or reject it with a code that doesn't say why.
This one tells you the actual reason at the call site.
The batch-sending trade-off, made explicit Symfony-provided transports send one email per request — that's the contract, and follows it like every other bridge.
Most third-party Symfony Mailer transports stop there: no batch path, full stop, one HTTP round-trip per recipient no matter ho