-
Notifications
You must be signed in to change notification settings - Fork 1
EmailRealizer
🌐 This page in: English · Português
Status: planned, not built. Nothing below ships today. This page is the design and the slices; the roadmap entry points here.
Write the email in C# with the same components a page is written with, and get back HTML an email client will actually render.
A component here is a VisualNode tree that knows nothing about its target. The web realizer turns
it into DOM and CSS; Photon turns it into GPU paint. Email is a third realizer — a target whose
rendering engine happens to be very restrictive, which is architecturally the same shape as a target
with no DOM at all.
Nothing about the authoring layer changes. The same Column, Text, Image and theme tokens, the
same Build(context).
Two pieces are in the tree today, verified by reading them:
-
WebRealizer.Lower(node, theme, typeScale, styles: null)— without a style sink, styles stay inline on the element instead of becoming deduplicated atomic classes. Inline style is email's hardest constraint, and this path already produces it. -
HtmlRenderer.RenderNode(...)turns the lowered tree into an HTML string.
So "C# component → HTML with inline styles" is two existing calls. What is missing is everything about the medium.
| Demand | Why | Where the web realizer differs |
|---|---|---|
Nested <table> layout |
Outlook on Windows renders with Word's engine: no flexbox, no grid |
Row/Column lower to FlexDirection
|
Inline style="" only |
<style> blocks and external CSS are stripped or ignored by several clients |
Atomic classes + a stylesheet (the sink path) |
| Literal colors | CSS custom properties are not resolved | Tokens emit var(--eq-color-…, fallback)
|
| Absolute image URLs or CID |
data: URIs are dropped by Gmail |
Assets assume a served origin |
| A constrained width | ~600px is the convention every client tolerates | Pages are fluid |
| No interaction | No JS, no hover in most clients, no scrolling containers |
Pressable, ScrollView, hover states exist |
A new package, eQuantic.UI.Email, depending only on Primitives and Core — never on
eQuantic.UI.Web, so the two realizers cannot drift into each other.
// Authored exactly like a page.
public sealed class WelcomeEmail(string name) : StatelessComponent
{
public override VisualNode Build(ComponentContext context) =>
Column(gap: Space.S4, children: [
Image(Assets.Logo, width: 132),
Text($"Welcome, {name}", TypeRole.Heading),
Button("Confirm your address", href: "https://…"),
]);
}
// Rendered where it is sent.
EmailMessage message = EmailRenderer.Render(new WelcomeEmail("Edgar"), theme);
// message.Html — table-lowered, fully inline
// message.PlainText — generated from the SAME tree, not written twiceEmailMessage carries both parts because a multipart message needs both, and generating the text
alternative from the tree is the only way it stays in step with the HTML.
M0 — Measure before building. Take a component that already exists, render it through the two calls above, and open the result in Gmail (web, iOS, Android), Outlook (Windows desktop, web) and Apple Mail. Produce a table of what survives untouched. This decides how big M1 is, and it is half a day. No new code.
M1 — Table lowering. Box, Row, Column and Gap become nested <table>/<tr>/<td> with
cell padding and spacer rows. Alignment maps to align/valign. This is the bulk of the track.
M2 — Inline styles and literal theming. Reuse the no-sink path; resolve every token to a literal color and length so nothing depends on custom properties. Density and type scale resolve once, at render.
M3 — Document shell. EmailDocument: doctype, <meta> set, the 600px container, the invisible
preheader line, and the dark-mode meta clients honour.
M4 — Vocabulary fence. Diagnostics naming what an email cannot do — ScrollView, Pressable,
Draggable, hover and focus styling, position: absolute. A Link becomes <a>; a Button
becomes a bulletproof <a> styled as a button. An Image without an absolute URL is an error, not a
broken picture in someone's inbox.
M5 — Plain-text alternative. Walk the same tree for the text/plain part.
-
No
<style>block, ever, even where a client supports it. One rule beats a matrix of exceptions. - No interactivity. An email is a printed page that happens to have links.
- No client-specific hacks in components. Anything Outlook needs lives in the realizer, never in something an author writes.
- Attachments and delivery are not ours. The output is HTML plus text; sending is the app's job (MailKit, SES, whatever it already uses).
The same two nets the rest of the framework uses:
- HTML pins, regenerated on purpose like the transpiled fixtures, so a layout change is visible as a diff rather than a surprise.
- A client matrix as conformance, run against real clients and recorded — the browser-is-the- arbiter rule applies here even harder, because email clients are further from a spec than browsers are.
🌐 English · Português
🏁 Start here
📱 Write-once
- Write-Once Components
- Declarative Surface
- Photon Engine
- Design System
- Capabilities
- Storage
- Forms
- Code Editor
- Markdown
- Mermaid
- Email Rendering
🏗️ Architecture
⚙️ Compilation
- Compiler
- Compile-Time Evaluation
- Supported C# Features
- External Type Resolution
- Build Flow
- Diagnostics
⚡ Runtime
🔌 Server
🎨 Ecosystem
🚀 Development