Mail and PDF Templates
Tagixo treats mail and PDF templates as first-class builder types alongside pages. They use the same module set (filtered per context), the same persistence shape, and the same drawer UI — but each ships its own native renderer tuned for its delivery medium.
Models and tables
Ccast\Tagixo\Models\MailTemplate— tabletgx_mail_templatesCcast\Tagixo\Models\PdfTemplate— tabletgx_pdf_templates
Both soft-deleted. Both share columns: name, slug, content (builder JSON), rendered_html, css, status, published_at, metadata.
MailTemplate adds subject and preheader. PdfTemplate adds paper_size (default A4), orientation (portrait / landscape), margin (default 2cm).
Native rendering
The whole point of these models is one method call:
$html = $mailTemplate->renderHtml();
// or
$html = $pdfTemplate->renderHtml();
What MailTemplate::renderHtml() does:
- Renders the builder JSON through
PageRendererwithcontext=mail - Wraps the output in an email-safe scaffold: DOCTYPE, viewport meta, mso-safe reset, hidden preheader (
display:none; mso-hide:all),<table role="presentation">wrapper at max-width 600px - Inlines all CSS via
TijsVerkoyen\CssToInlineStylesso the result works in clients that strip<style>blocks (Gmail web, Outlook)
What PdfTemplate::renderHtml() does:
- Renders through
PageRendererwithcontext=pdf - Emits
@pagerules driven bypaper_size,orientation,margin - Returns a print-ready HTML document — pipe it through your PDF engine of choice
Both are safe to call from queued jobs. They work off the persisted content JSON without booting the admin UI.
Builder admin endpoints
When config('tagixo.enable_default_types') is true, the package auto-registers two default BuilderTypeContract implementations:
DefaultMailType→/tagixo/manage/mails/*DefaultPdfType→/tagixo/manage/pdfs/*
That gives you list / create / edit / data / save endpoints with zero code. For custom workflows, register your own handler under the same key:
BuilderTypeRegistry::register('mails', \App\Builder\Types\MyMailType::class);
If you use the Filament or Primix SDK, the SDK's MailTemplateResource (when present) covers the same flow with native admin UI — you typically don't reference /tagixo/manage/mails/* directly.
Context-filtered module set
Both builders use ComponentRegistry::forContext($context) to filter the available modules. Modules unsafe in email clients (or non-renderable in PDFs) are excluded by default:
video— most clients block embedstabs— JavaScript not allowedaccordion— sameslider— samepopup— same
A module declares its allowed contexts via ModuleDefinition::contexts(['page', 'mail', 'pdf']). Module classes without an explicit declaration default to ['page'], so they won't appear in mail/pdf unless you opt them in.
You can scaffold a new module pre-configured for mail and PDF:
php artisan make:tagixo-module ProductCard --context=page,mail,pdf
Consumer usage — Mail
Simple delivery
use Ccast\Tagixo\Models\MailTemplate;
use Illuminate\Support\Facades\Mail;
$template = MailTemplate::findOrFail($id);
Mail::html(
$template->renderHtml(),
fn ($m) => $m->to('customer@example.com')->subject($template->subject)
);
As a Mailable
class WelcomeMail extends Mailable
{
public function __construct(public MailTemplate $template) {}
public function build()
{
return $this->subject($this->template->subject)
->html($this->template->renderHtml());
}
}
Consumer usage — PDF
The package does NOT ship a PDF engine. renderHtml() returns the HTML; you pick the engine.
dompdf
use Dompdf\Dompdf;
$dompdf = new Dompdf();
$dompdf->loadHtml($pdfTemplate->renderHtml());
$dompdf->setPaper($pdfTemplate->paper_size, $pdfTemplate->orientation);
$dompdf->render();
return $dompdf->output();
Browsershot (Chromium-based, higher fidelity)
use Spatie\Browsershot\Browsershot;
return Browsershot::html($pdfTemplate->renderHtml())
->format($pdfTemplate->paper_size)
->landscape($pdfTemplate->orientation === 'landscape')
->pdf();
Merge tags and dynamic data
The plugin does NOT include built-in merge-tag resolution. The output of renderHtml() is a fully-rendered HTML string. If you need {{user.name}} style substitution, run a simple post-processing pass before delivery:
$html = $template->renderHtml();
$html = str_replace(
['{{user.name}}', '{{order.id}}'],
[$user->name, $order->id],
$html
);
Mail::html($html, fn ($m) => $m->to($user->email)->subject($template->subject));
For richer logic, wrap that in a small MailTemplateRenderer service in your app — the rendering half stays in the package, your business logic stays in your application code.
Save-time pre-baking
The save pipeline pre-bakes rendered_html and css on the record when the user clicks Save. That is good for caching but renderHtml() regenerates the full document on demand, so updates to subject or preheader (or to the email scaffold itself in a package upgrade) take effect without a re-save.
If you serve emails from queued jobs and want maximum throughput, you can read directly from the cached rendered_html column instead of calling renderHtml() each time — at the cost of skipping the runtime scaffold regeneration.