Repository navigation
EmailRealizer es
🌐 Esta página en: English · Português · Español · Français · Deutsch
Desde 0.2.0-preview.45
Estado: el núcleo SE ENTREGÓ en la 0.2.0-preview.45 (mergeado el 2026-08-25).
EmailRealizeryEmailRendererexisten eneQuantic.UI.Emailcon el vocabulario de abajo. Todavía por delante: la matriz de clientes reales (un ejemplo generado contra Gmail, Outlook y Apple Mail) y los pins de HTML una vez que la salida se asiente.
Escribe el email en C# con los mismos componentes con los que se escribe una página, y recibe HTML que un cliente de email va a renderizar de verdad.
Un componente aquí es un árbol de VisualNode que no sabe nada de su destino. El realizador web lo
convierte en DOM y CSS; Photon lo convierte en pintura de GPU. El email es un tercer
realizador: un destino cuyo motor de renderizado resulta ser muy restrictivo, lo que
arquitectónicamente tiene la misma forma que un destino sin DOM alguno.
Nada de la capa de autoría cambia. Los mismos Column, Text, Image y tokens de tema, el mismo
Build(context).
Hoy hay dos piezas en el árbol, verificadas leyéndolas:
-
WebRealizer.Lower(node, theme, typeScale, styles: null): sin el sink de estilos, los estilos se quedan inline en el elemento en lugar de convertirse en clases atómicas deduplicadas. El estilo inline es la restricción más dura del email, y este camino ya lo produce. -
HtmlRenderer.RenderNode(...)convierte el árbol bajado en una cadena de HTML.
Así que "componente C# → HTML con estilos inline" son dos llamadas que ya existen. Lo que falta es todo lo relativo al medio.
| Exigencia | Por qué | En qué difiere el realizador web |
|---|---|---|
Layout con <table> anidadas |
Outlook en Windows renderiza con el motor de Word: sin flexbox, sin grid |
Row/Column bajan a FlexDirection
|
Solo style="" inline |
Los bloques <style> y el CSS externo los quitan o los ignoran varios clientes |
Clases atómicas + una hoja de estilos (el camino con sink) |
| Colores literales | Las custom properties de CSS no se resuelven | Los tokens emiten var(--eq-color-…, fallback)
|
| URLs de imagen absolutas o CID | Gmail descarta las URIs data:
|
Los assets suponen un origen servido |
| Un ancho acotado | ~600px es la convención que todo cliente tolera | Las páginas son fluidas |
| Ninguna interacción | Sin JS, sin hover en la mayoría de clientes, sin contenedores con scroll |
Pressable, ScrollView y los estados de hover existen |
Un paquete nuevo, eQuantic.UI.Email, que depende solo de Primitives y Core, nunca de
eQuantic.UI.Web, para que los dos realizadores no puedan desviarse el uno hacia el otro.
// Escrito exactamente como una página.
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://…"),
]);
}
// Renderizado allí donde se envía.
EmailMessage message = EmailRenderer.Render(new WelcomeEmail("Edgar"), theme);
// message.Html bajado a tablas, totalmente inline
// message.PlainText generado del MISMO árbol, no escrito dos vecesEmailMessage lleva las dos partes porque un mensaje multipart necesita las dos, y generar la
alternativa de texto a partir del árbol es la única manera de que se mantenga al paso del HTML.
M0: medir antes de construir. Hecha, y cambió el plan. Además de los dos hallazgos esperados
(el layout flex, los colores light-dark()), un tercero que el plan no tenía: la tipografía vive
en clases eq-type-*, y un email no tiene hoja de estilos, así que todo el texto se renderizaba a
un solo tamaño. M2 creció para poner la rampa del tema inline en cada texto. La mitad restante de M0
es humana: el ejemplo generado contra buzones reales.
M1: bajar a tablas. Hecha. Box, Row, Column y Gap se vuelven <table>/<tr>/<td>
anidadas con padding de celda y filas espaciadoras. La alineación se mapea a align/valign. Esto
es el grueso de la vía.
M2: estilos inline y tema literal. Hecha (más la rampa tipográfica inline). Reutiliza el camino sin sink; resuelve cada token a un color y una longitud literales para que nada dependa de custom properties. La densidad y la escala tipográfica se resuelven una vez, al renderizar.
M3: casca del documento. Hecha. EmailDocument: doctype, el conjunto de <meta>, el
contenedor de 600px, la línea de preencabezado invisible, y el meta de modo oscuro que los clientes
respetan.
M4: cerca del vocabulario. Hecha. Diagnósticos que nombran lo que un email no puede hacer:
ScrollView, Pressable, Draggable, el estilo de hover y de focus, position: absolute. Un
Link se vuelve <a>; un Button se vuelve un <a> a prueba de balas con estilo de botón. Una
Image sin URL absoluta es un error, no una imagen rota en la bandeja de alguien.
M5: alternativa en texto puro. Hecha. Recorrer el mismo árbol para la parte text/plain.
La porción pasó por cinco rondas de revisión (32 hallazgos) y lo que sobrevivió son reglas que vale declarar:
-
Una propiedad que el medio no puede expresar se valla, no se aproxima: la tinta en degradado y
MaxLineslanzan nombrando la salida, porque un sólido en su lugar es otra tinta que nadie eligió, y mostrar más texto del que el autor acotó es una divergencia de contenido. -
Una propiedad que el medio expresa de otra manera se honra: el padding del contenedor (una
tabla envoltorio de una celda),
Crossy elAlignSelfpor hijo (alineación de celda), los radios por esquina, los bordes, los elementos de encabezado, elaria-labelque viene deLink.Label. -
MainAlignes vacuo aquí, documentado en lugar de vallado: una tabla de email se dimensiona a su contenido, así que no hay espacio libre que distribuir, y entonces todo valor renderiza igual, lo cual es fiel. -
Un componente roto hace fallar el ENVÍO. Los componentes se expanden vía
Build, no vía elBuildContainedde la web: una caja que describe el error es lo correcto en una página en vivo que quien programa está mirando, y es incorrecta en un mensaje a punto de llegar a la bandeja de alguien disfrazada de contenido. Desde la 0.2.0-preview.55 un ciclo lo hace fallar igual: el recorrido comparte la cuenta de profundidad de la costura a través deComponentBoundary.Enter, y pasado el límite lanza unaInvalidOperationExceptionen lugar de contener. Antes, un componente que se construía a sí mismo era unaStackOverflowException, que nada puede capturar, y el proceso de envío moría sin reportar nada. -
Las direcciones se parsean, no se comprueban por prefijo; los valores de atributo se codifican
como atributo; un
Linkalrededor de runs enlazados se niega a anidar anclas, nombrando la elección.
-
Ningún bloque
<style>, nunca, incluso donde un cliente lo soporte. Una regla le gana a una matriz de excepciones. - Ninguna interactividad. Un email es una página impresa que resulta tener enlaces.
- Ningún hack específico de cliente en los componentes. Todo lo que Outlook necesita vive en el realizador, nunca en algo que escriba quien escribe.
- Los adjuntos y la entrega no son nuestros. La salida es HTML más texto; enviar es trabajo de la aplicación (MailKit, SES, lo que ya use).
Las mismas dos redes que usa el resto del framework:
- Pins de HTML, regenerados a propósito como los fixtures transpilados, para que un cambio de layout sea visible como un diff y no como una sorpresa.
- Una matriz de clientes como conformidad, ejecutada contra clientes reales y registrada: la regla de que el navegador es el árbitro se aplica aquí incluso con más fuerza, porque los clientes de email están más lejos de una especificación que los navegadores.
🏁 Empieza aquí
🏗️ Arquitectura
- Visión general de la arquitectura
- Componentes write-once
- Superficie declarativa
- Arquitectura de paquetes
- Componentes
- Estilos
- Localización
- Analytics y GTM
📱 Write-once
- Motor Photon
- Design System
- Capacidades
- Almacenamiento
- Formularios
- Editor de código
- Markdown
- Mermaid
- Renderizado de email
⚙️ Compilación
⚡ Runtime
🔌 Servidor
🎨 Ecosistema
🚀 Desarrollo