Email & Newsletter Templates Architecture
Beta - APIs may change
This module is in beta. The classes, plugins, database schema, and component conventions described in the developer documentation may change in backwards-incompatible ways before the general release. Customizations built against them may need updating between beta versions.
This page explains how Hyva_EmailTemplates (hyva-themes/commerce-module-email-templates) turns a Liveview component tree into the HTML that Magento actually sends. Read this before customizing render behavior or debugging why an email looks different from its preview.
Content Types and Providers
The module registers two Liveview content types on the Hyvä CMS ProviderPool:
| Content type | Provider | Backing entity |
|---|---|---|
email_template |
Hyva\EmailTemplates\Model\Provider\EmailTemplateProvider |
Magento\Email\Model\Template |
newsletter_template |
Hyva\EmailTemplates\Model\Provider\NewsletterTemplateProvider |
Magento\Newsletter\Model\Template |
Per-template state (Hyvä editor enabled flag, draft and published component trees, preheader) lives in the module's own tables, hyva_commerce_email_template and hyva_commerce_newsletter_template, each with a cascading foreign key to its Magento parent. The Magento template rows stay untouched, which is why disabling the Hyvä editor cleanly falls back to classic rendering.
Editor routes are registered in etc/adminhtml/di.xml on the EditorRouteRegistry: hyva_cms_email/liveview/editor (email) and hyva_cms_email/liveview/newslettereditor (newsletter).
The Render Pipeline
Both content types share one core service: Model\Render\ComponentTreeRenderer renders the saved component tree to HTML by rendering each component's element template (Hyva\CmsLiveviewEditor\Block\Element + the component's .phtml).
From there, the two types diverge - and the difference matters:
Email: afterGetProcessedTemplate
Plugin\Model\Template plugs into Magento\Email\Model\Template. When a Hyvä-enabled template renders, the plugin builds the full email HTML (component tree → chrome header/footer → root.phtml wrapper), runs it through the Magento email template filter (which resolves {{var}}, {{trans}}, {{layout}}, {{depend}} directives with the real transactional variables), and inlines CSS.
Newsletter: afterGetTemplateText
Plugin\Model\NewsletterTemplate plugs into getTemplateText() instead, and deliberately returns HTML with {{var}} directives still intact. Magento's newsletter queue snapshots getTemplateText() into newsletter_queue.newsletter_text when a campaign is queued; the per-subscriber newsletter filter then resolves each subscriber's personal variables (name, unsubscribe link) at send time. Resolving variables too early would bake one subscriber's data into everyone's newsletter.
Hook placement is load-bearing
If you plug into these models yourself, respect this split: email resolves directives in getProcessedTemplate, newsletter must leave them for the queue. Swapping the hooks breaks personalization.
CSS Inlining and Directive Safety
Model\InlineStyleProcessor inlines <style> rules into element attributes (Gmail strips <style> blocks). Before parsing, it placeholder-protects {{...}} directives so DOMDocument cannot corrupt them - directives survive inlining in both text and attributes.
Preview Pipeline
The editor preview (hyva_cms_email/create/emailpreview, Block\Preview\EmailWrapper) renders the same component tree but resolves directives with dummy data instead of the Magento filter:
Plugin\Block\EmailOrderContextInjectorinjectsorder_preview_context(customer, items, totals, addresses) into every element block - from a real order whenPreview Order (Increment ID)is configured, otherwise from fixtures.Plugin\Block\EmailHtmlVariableSubstitutor+Model\DummyData\VariableSubstitutorresolve{{var}},{{trans}},{{depend}},{{if}}and{{layout}}directives against a dummy variable map (see the class for the full list, includingorder_data.*andsubscriber_data.*).
Element templates can branch on $block->validPreview() when preview needs different markup than production (for example, the Shipment Tracking component renders dummy rows in preview and a {{layout}} directive in production).
Version History
hyva_commerce_email_template_version_history stores versions for both content types, discriminated by an entity_type column (email_template / newsletter_template). The table intentionally has no foreign key (one column cannot reference two parents); cleanup happens at application level via Model\VersionHistoryCleaner, triggered by the delete plugins on both template models.
Newsletter Chrome
Model\Render\NewsletterChrome appends the default newsletter footer (store details + unsubscribe link) and optional logo header, controlled by hyva_cms/newsletter_template/use_footer / use_header. Both the send path and the preview use this one service, so what you preview is what gets sent. The templates are theme-overridable:
Hyva_EmailTemplates::newsletter/header/default.phtmlHyva_EmailTemplates::newsletter/footer/default.phtml
Component Scoping
All bundled components declare "context_flags": ["email"] in etc/hyva_cms/components.json, and the email/newsletter editors declare allowed_root_component_context_flags: ['email'] plus strict_component_context: true. The result: email components never appear in CMS page/block pickers, and generic CMS components never appear inside emails (they would render non-email-safe markup). See Creating Email Components for what this means for your own components.
Related Topics
- Creating Email Components - Build your own email-safe components
- Hyvä CMS Architecture Overview - The Liveview editor foundations
- Configuration - Store-level settings these services read