You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Rework EMailTemplate: blocks rendered at the end, one template file per block, shared design tokens #65330
After the email redesign for 36 (#65193, #65194, #65312 and the PRs moving mails over), lib/private/Mail/EMailTemplate.php has become hard to change, and hard for customers to customise. Ten PRs in, these are the parts that slowed us down:
Today
What it cost
The HTML lives in ~35 PHP heredocs filled with positional vsprintf (%7\$s)
Every visual tweak is a careful edit of long strings
Colors, radius and font stack repeated in about 50 inline styles, dark mode as a separate set of rules in <head>
The NcNoteCard colors (#65222) and dark mode links touched dozens of places
bodyOpened / bodyListOpened / footerAdded decide where HTML gets appended
Headings and the sender block have to close the body by hand
addBodyText($text, $plainText) silently treats $text as raw HTML when $plainText is given
That's how the &amp; heading bug happened (#65219)
Right-to-left mirroring is a regex over the finished HTML (#65312)
Works, but it's a workaround
Byte-exact whole-email fixtures, plus full HTML copied into app tests (NewUserMailHelperTest)
Each style change rewrites 5+ fixtures
Customers customise by copying and patching the protected HTML strings (mail_template_class)
Every redesign breaks their templates, see nextcloud-gmbh/email_template_example#10
Note
mail_template_class stays the supported way for customers to customise emails, and gets simpler. The public API (IEMailTemplate, EMailDetails) doesn't change.
Proposal, in four PRs:
Blocks first, render last. Each add*() call appends a small block object, renderHtml() and renderText() walk the list at the end. Each block also knows its plain text. No more open/close state, HTML and text can't drift apart. Pure refactor: the existing fixtures must stay byte-identical.
One template file per block in core/templates/mail/ (layout.php, heading.php, note.php, details.php...), rendered with ITemplateManager and p(), so escaping is the default.
Design tokens defined once (text, muted, borders, backgrounds, note colors, radius, font) in a small EMailStyle, used by the templates and the dark mode rules. Direction is a token too (start/end), which replaces the regex mirroring.
Tests per block: a small fixture per block and direction, a couple of full email snapshots, and app tests asserting template calls instead of comparing HTML.
With that, a customer's mail_template_class can customise at the level it needs, without copying HTML:
Level
How
Survives a redesign
Branding
Theming app: logo, color, name, slogan. No code
Yes
Look and feel
Override getStyle() and change tokens: font, colors, radius, backgrounds
Yes
One block's markup
Ship templates/mail/<block>.php in the customer's app, set $templateApp. Lookup falls back to core for every other block
Mostly, if the block's parameters stay
Behaviour per email
Override add*() methods, use $this->emailId and $this->data, as today
Yes
These extension points (getStyle(), the token names, each block template's parameters, the add*() methods) become a documented contract with @since, covered by a test rendering a sample customer subclass.
Existing subclasses that override the old HTML strings get a compatibility layer for one release: an overridden string like $header is still used for its block, with a deprecation warning in the log pointing to the matching template file. It's removed the release after. Subclasses overriding methods need nothing.
Customer material to update along the way: the email_template_example app (one small example per level), the admin manual section on mail_template_class, and the portal article on customised email templates.
Not planned: a CSS inliner library or MJML. Plain PHP tokens are enough and add no dependency.
After the email redesign for 36 (#65193, #65194, #65312 and the PRs moving mails over),
lib/private/Mail/EMailTemplate.phphas become hard to change, and hard for customers to customise. Ten PRs in, these are the parts that slowed us down:vsprintf(%7\$s)<head>bodyOpened/bodyListOpened/footerAddeddecide where HTML gets appendedaddBodyText($text, $plainText)silently treats$textas raw HTML when$plainTextis given&amp;heading bug happened (#65219)NewUserMailHelperTest)mail_template_class)Note
mail_template_classstays the supported way for customers to customise emails, and gets simpler. The public API (IEMailTemplate,EMailDetails) doesn't change.Proposal, in four PRs:
add*()call appends a small block object,renderHtml()andrenderText()walk the list at the end. Each block also knows its plain text. No more open/close state, HTML and text can't drift apart. Pure refactor: the existing fixtures must stay byte-identical.core/templates/mail/(layout.php,heading.php,note.php,details.php...), rendered withITemplateManagerandp(), so escaping is the default.EMailStyle, used by the templates and the dark mode rules. Direction is a token too (start/end), which replaces the regex mirroring.With that, a customer's
mail_template_classcan customise at the level it needs, without copying HTML:getStyle()and change tokens: font, colors, radius, backgroundstemplates/mail/<block>.phpin the customer's app, set$templateApp. Lookup falls back to core for every other blockadd*()methods, use$this->emailIdand$this->data, as todayThese extension points (
getStyle(), the token names, each block template's parameters, theadd*()methods) become a documented contract with@since, covered by a test rendering a sample customer subclass.Existing subclasses that override the old HTML strings get a compatibility layer for one release: an overridden string like
$headeris still used for its block, with a deprecation warning in the log pointing to the matching template file. It's removed the release after. Subclasses overriding methods need nothing.Customer material to update along the way: the email_template_example app (one small example per level), the admin manual section on
mail_template_class, and the portal article on customised email templates.Not planned: a CSS inliner library or MJML. Plain PHP tokens are enough and add no dependency.