kassoulet/swiftmarkbundle
SwiftMarkBundle brings SwiftMark integration to Symfony, simplifying Markdown rendering for templates, services, and Markdown-based emails. Install via Composer, configure the bundle, and add clean Markdown workflows to your app without custom wiring.
Correct the premise before you install anything. Despite being evaluated in a Laravel context, kassoulet/swiftmarkbundle is a Symfony bundle built on SwiftMailer. There is no service provider, no facade, no config/swiftmark.php, and no Markdown mailable hook. Laravel's mail stack (Illuminate\Mail) does not consume Bundle subclasses, DependencyInjection/Extension classes, or Symfony service YAML. The realistic Laravel outcome is one of two things: pull the underlying renderer class out and use it as a library, or — much more likely — rebuild the capability in an afternoon on league/commonmark and Laravel's native Markdown mailables.
Verify installability first. The name may not resolve on Packagist, and even if it does, its require block likely pins SwiftMailer and an older Symfony major, which will collide with a modern Laravel app:
composer show kassoulet/swiftmarkbundle --all
If that returns nothing, add the VCS repo and pin a commit rather than a floating branch:
{
"repositories": [
{ "type": "vcs", "url": "https://github.com/Jb-31/KassouletSwiftMarkBundle" }
],
"require": { "kassoulet/swiftmarkbundle": "dev-master" }
}
Expect dependency conflicts. SwiftMailer reached end-of-life in November 2021 and Laravel 9+ dropped it. If Composer refuses to resolve, that is the package telling you the answer.
Where to look first, in order:
composer.json — symfony/* and swiftmailer/swiftmailer constraints. This tells you the target Symfony version and confirms the EOL transport.Resources/config/services.yml (or config/services.yaml) — the authoritative list of service IDs, constructor arguments, and tags. Almost every "how do I use this" question is answered here, not in the README.DependencyInjection/Configuration.php — actual config keys and defaults (template path, Markdown flavor, layout wrapping).Kassoulet\SwiftMark\Renderer) and any Swift_Events_*Listener / Swift_Plugins_DecoratorPlugin subclass — the listener, if present, is the intended send-pipeline hook.First use case — render one Markdown string to HTML, in Laravel, without the bundle:
// AppServiceProvider::register() — direct use of the bundle's renderer as a plain class
$this->app->singleton('swiftmark', function () {
// Copy constructor args verbatim from the bundle's services.yml.
return new \Kassoulet\SwiftMark\Renderer(/* markdown parser, options, theme path */);
});
$html = app('swiftmark')->render("# Hello\n\n**World**");
If that produces HTML, you understand the whole capability. The native Laravel equivalent is shorter and maintained:
use League\CommonMark\CommonMarkConverter;
$html = (new CommonMarkConverter([
'html_input' => 'strip',
'allow_unsafe_links' => false,
]))->convert("# Hello\n\n**World**")->getContent();
That is effectively the package. Budget a day to confirm it and you can close the evaluation.
The only thing worth extracting is the renderer. Register it as a singleton in AppServiceProvider, copying constructor arguments from the bundle's services.yml. This keeps the dependency surface to one class you can replace later, and avoids dragging Symfony DI into a Laravel app.
// AppServiceProvider::register()
$this->app->singleton(MarkdownRenderer::class, function () {
return new MarkdownRenderer(new CommonMarkConverter([...]));
});
Do not try to load the Bundle class or its extension. That path leads to manually bootstrapping a Symfony container, which is not a thing you want in a Laravel app.
If you do adopt the renderer, hide it behind a Mailable so callers never touch Markdown directly:
class WelcomeEmail extends Mailable
{
use Queueable, SerializesModels;
public function __construct(public User $user) {}
public function build()
{
$md = file_get_contents(resource_path('mail/welcome.md'));
$html = app('swiftmark')->render($md);
return $this->subject('Welcome')
->html($html)
->text($this->toPlainText($html));
}
private function toPlainText(string $html): string
{
return (new \League\HTMLToMarkdown\HtmlConverter())->convert($html);
}
}
The ->text() half is where most Markdown-to-email implementations quietly break: strip_tags() destroys links and list structure. Use an HTML→text converter, or render the source twice (Markdown→text via a plain renderer, Markdown→HTML via the converter).
Laravel has shipped Markdown mailables since 5.7, backed by league/commonmark. This is the idiomatic equivalent and gets security patches:
public function build()
{
return $this->markdown('mail.welcome', ['user' => $this->user]);
}
The Blade view at resources/views/mail/welcome.blade.php contains Markdown with Blade interpolation, and Laravel renders both HTML and plain-text parts. Extensions (tables, footnotes, strikethrough) are configurable through the mail.markdown config in config/mail.php.
Register the bundle in config/bundles.php, configure it under config/packages/, and use the tagged SwiftMailer decorator/listener if one exists so rendered output is applied during send() rather than by manual render() calls. This is the only context where the package's design makes sense — and only if Symfony Mailer migration is genuinely off the table.
league/commonmark's. If they match, the bundle adds nothing.html_input => 'strip' and allow_unsafe_links => false, or run output through HtmlSanitizer. The bundle is unmaintained; do not assume it does this..txt template.Mail::fake() in tests so you assert on rendered content, not on sends.Bundle cannot be installed into Laravel. Anything that looks like "integration" is you reading the source and re-implementing. Treat that as the default plan, not a fallback.app/Support/ if you keep it, rather than leaving it as a Composer line item nobody can justify in six months.kassoulet.swiftmark.renderer (or whatever the real ID is) is only known from services.yml. Do not guess; read the file. If the bundle renames it in a minor release, nothing in your app catches the break until runtime.javascript:), raw HTML passthrough, and image sources are all real attack vectors. An unmaintained parser will not have kept pace. Configure commonmark conservatively regardless of which renderer you use.strip_tags() output looks fine in logs and terrible in mail clients. Test the text part explicitly — inspect the text/plain MIME part, not the debug HTML.symfony/* and swiftmailer/swiftmailer conflict with your lockfile, do not add --ignore-platform-reqs or a shim. That is the package correctly telling you it does not belong in a modern stack.dd($html) in a local-only branch, or write to a file), and use MAIL_MAILER=log so you can diff what actually would have been sent. Comparing rendered output to a hand-written HTML email is the fastest way to spot a broken conversion.league/commonmark extensions for tables, footnotes, autolinks, and strikethrough are config flags. Add them as needed — that is the entire feature surface you would be missing by not adopting this bundle.How can I help you explore Laravel packages today?