Pure-function render pipeline: HEEx → plaintext → CSS inlining → data-mg-* strip.
All functions are side-effect free. No processes, no DB, no HTTP calls.
Pipeline (D-15 — plaintext runs BEFORE CSS inlining)
render_html/2— callsMailglass.TemplateEngine.HEEx.render/3, returns HTML iodatato_plaintext/1— custom Floki walker on the pre-VML logical HTMLinline_css/1—Premailex.to_inline_css/2(preserves MSO conditionals per D-14)strip_mg_attributes/1— removes alldata-mg-*from the final HTML wire
Plaintext MUST run on step-1 output (pre-CSS-inlining HTML), NOT on step-3 output. Premailex adds VML wrappers; the plaintext walker must not see them.
Performance Target
< 50ms end-to-end for a typical template (AUTHOR-03).
Boundary
Mailglass.Renderer cannot depend on Mailglass.Outbound, Mailglass.Repo,
or any process. This is enforced by the :boundary compiler (CORE-07).
Summary
Functions
Renders a Mailglass.Message through the full pipeline.
Extracts plaintext from HTML using data-mg-plaintext strategy attributes.
Functions
@spec render( Mailglass.Message.t(), keyword() ) :: {:ok, Mailglass.Message.t()} | {:error, Mailglass.TemplateError.t()}
Renders a Mailglass.Message through the full pipeline.
Takes a Message whose swoosh_email.html_body is either a HEEx function
component (fn assigns -> ~H"..." end) or a pre-rendered HTML string. Runs
the configured pipeline and returns a Message with swoosh_email.html_body
replaced by the final inlined HTML and swoosh_email.text_body populated
with the auto-generated plaintext.
The entire pipeline is wrapped in Mailglass.Telemetry.render_span/2.
Metadata is whitelisted to %{tenant_id, mailable} — no PII per D-31.
Examples
component = fn _assigns -> ~H|<p>Hello</p>| end
email = %Swoosh.Email{html_body: component}
message = Mailglass.Message.new(email, mailable: MyMailer)
{:ok, rendered} = Mailglass.Renderer.render(message)
Extracts plaintext from HTML using data-mg-plaintext strategy attributes.
Strategies (D-22):
"skip"— excludes the element and its children (preheader)"link_pair"— emits"Label (url)"(button, link)"divider"— emits"\n---\n"(hr)"heading_block_1"— uppercase + blank lines (h1)"heading_block_2"/"_3"/"_4"— title case + blank lines"text"— raw text content; for<img>, uses the alt attribute- anything else (including missing) — recurses into children
Runs on the pre-VML logical HTML tree so VML artifacts never leak into plaintext output.