Skip to content

EmailRealizer pt BR

Edgar Mesquita edited this page Aug 25, 2026 · 2 revisions

Renderização de Email (Track M)

🌐 Esta página em: English · Português

Estado: o núcleo está CONSTRUÍDO (merge em 2026-08-25; sai no próximo release). EmailRealizer e EmailRenderer existem em eQuantic.UI.Email com o vocabulário abaixo. Ainda à frente: a matriz de clientes reais (um sample gerado contra Gmail/Outlook/Apple Mail) e os pins de HTML quando a saída assentar.

Escrever o email em C# com os mesmos componentes com que se escreve uma página, e receber de volta um HTML que um cliente de email realmente renderiza.

Por que isso encaixa na arquitetura em vez de brigar com ela

Um componente aqui é uma árvore de VisualNode que não sabe nada sobre seu alvo. O realizador web a transforma em DOM e CSS; o Photon a transforma em pintura na GPU. Email é um terceiro realizador — um alvo cujo motor de render é muito restritivo, o que é arquiteturalmente a mesma forma de um alvo sem DOM nenhum.

Nada na camada de autoria muda. Os mesmos Column, Text, Image e tokens de tema, o mesmo Build(context).

O que já existe

Duas peças estão na árvore hoje, verificadas lendo o código:

  • WebRealizer.Lower(node, theme, typeScale, styles: null) — sem o sink de estilos, os estilos ficam inline no elemento em vez de virarem classes atômicas deduplicadas. Estilo inline é a restrição mais dura do email, e esse caminho já o produz.
  • HtmlRenderer.RenderNode(...) transforma a árvore rebaixada numa string de HTML.

Ou seja, "componente C# → HTML com estilos inline" são duas chamadas que já existem. O que falta é tudo o que diz respeito ao meio.

O que o email realmente exige

Exigência Por quê Onde o realizador web difere
Layout em <table> aninhada O Outlook no Windows renderiza com o motor do Word: sem flexbox, sem grid Row/Column descem para FlexDirection
style="" inline Blocos <style> e CSS externo são removidos ou ignorados por vários clientes Classes atômicas + folha de estilo (o caminho do sink)
Cores literais Custom properties do CSS não são resolvidas Tokens emitem var(--eq-color-…, fallback)
URL absoluta de imagem ou CID URIs data: são descartadas pelo Gmail Assets assumem uma origem servida
Largura restrita ~600px é a convenção que todo cliente tolera Páginas são fluidas
Nenhuma interação Sem JS, sem hover na maioria dos clientes, sem contêiner rolável Pressable, ScrollView e estados de hover existem

Desenho

Um pacote novo, eQuantic.UI.Email, dependendo apenas de Primitives e Core — nunca de eQuantic.UI.Web, para que os dois realizadores não possam derivar um para dentro do outro.

// Escrito exatamente como uma 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($"Bem-vindo, {name}", TypeRole.Heading),
            Button("Confirme seu endereço", href: "https://…"),
        ]);
}

// Renderizado onde é enviado.
EmailMessage message = EmailRenderer.Render(new WelcomeEmail("Edgar"), theme);
// message.Html          — rebaixado para tabelas, totalmente inline
// message.PlainText     — gerado da MESMA árvore, não escrito duas vezes

EmailMessage carrega as duas partes porque uma mensagem multipart precisa das duas, e gerar a alternativa em texto a partir da árvore é o único jeito de ela ficar em dia com o HTML.

Fatias

M0 — Medir antes de construir. Feita — e mudou o plano. Além dos dois achados esperados (layout flex, cores light-dark()), um terceiro que o plano não tinha: a tipografia vive nas classes eq-type-*, e um email não tem folha de estilos, então todo texto saía num tamanho só. A M2 cresceu para inlinar a rampa do tema em cada texto. A metade restante da M0 é humana: o sample gerado contra caixas de correio reais.

M1 — Rebaixar para tabelas. Feita. Box, Row, Column e Gap viram <table>/<tr>/<td> aninhados, com padding de célula e linhas espaçadoras. Alinhamento mapeia para align/valign. É o grosso do track.

M2 — Estilos inline e tema literal. Feita (mais a rampa de tipo inlinada). Reusar o caminho sem sink; resolver todo token para uma cor e um comprimento literais, para nada depender de custom properties. Densidade e escala de tipo resolvem uma vez, na renderização.

M3 — Casca do documento. Feita. EmailDocument: doctype, o conjunto de <meta>, o contêiner de 600px, a linha invisível de preheader, e o meta de modo escuro que os clientes honram.

M4 — Cerca do vocabulário. Feita. Diagnósticos nomeando o que um email não faz — ScrollView, Pressable, Draggable, estilo de hover e foco, position: absolute. Um Link vira <a>; um Button vira um <a> à prova de balas estilizado como botão. Uma Image sem URL absoluta é um erro, não uma figura quebrada na caixa de entrada de alguém.

M5 — Alternativa em texto puro. Feita. Percorrer a mesma árvore para a parte text/plain.

O que a revisão ensinou ao contrato

A fatia passou por cinco rondas de revisão (32 achados) e o que sobreviveu são regras que valem ser ditas:

  • Propriedade que o meio não expressa é cercada, não aproximada: tinta em gradiente e MaxLines atiram nomeando a saída — um sólido no lugar é outra tinta que ninguém escolheu, e mostrar mais texto do que o autor limitou é divergência de conteúdo.
  • Propriedade que o meio expressa diferente é honrada: padding de contêiner (tabela-envelope de uma célula), Cross e AlignSelf por filho (alinhamento da célula), raios por canto, bordas, elementos de heading, aria-label do Link.Label.
  • MainAlign é vácuo aqui, documentado em vez de cercado: uma tabela de email dimensiona-se ao conteúdo, então não há espaço livre a distribuir — todo valor renderiza igual, o que é fiel.
  • Um componente quebrado falha o ENVIO. Componentes expandem via Build, não o BuildContained do web: uma caixa de erro é certa numa página viva que um developer está olhando e errada numa mensagem prestes a chegar à caixa de entrada de um leitor vestida de conteúdo.
  • Endereços são parseados, não checados por prefixo; valores de atributo são codificados; um Link em volta de runs com link recusa aninhar âncoras, nomeando a escolha.

Cercas

  • Nunca um bloco <style>, mesmo onde um cliente o suporte. Uma regra vence uma matriz de exceções.
  • Sem interatividade. Um email é uma página impressa que por acaso tem links.
  • Sem gambiarra por cliente dentro dos componentes. O que o Outlook precisa mora no realizador, nunca em algo que um autor escreve.
  • Anexos e envio não são nossos. A saída é HTML mais texto; enviar é trabalho da app (MailKit, SES, o que ela já usa).

Como se testa

As mesmas duas redes que o resto do framework usa:

  • Pins de HTML, regenerados de propósito como as fixtures transpiladas, para que uma mudança de layout apareça como diff e não como surpresa.
  • Uma matriz de clientes como conformidade, rodada contra clientes reais e registrada — a regra do "o browser é o árbitro" vale aqui ainda mais forte, porque clientes de email estão mais longe de uma especificação do que browsers estão.

Clone this wiki locally