Skip to content

Email Generator

The email generator produces brand-on marketing emails from a short brief. It is a kept v1 feature: the UI is now served by Next.js, but the pipeline still runs on the Express server, independently of the Director video workflow.

Served by Next.js, backed by Express

The page lives at /email in the Next.js app (web/src/app/(shell)/email/page.tsxweb/src/legacy/pages/EmailFlyer.tsx). All /api/email/* calls are proxied to the Express server (:3001) via next.config rewrites — the pipeline code below is unchanged from v1.

Entry point: server/src/pipeline/emailPipeline.ts UI: web/src/legacy/pages/EmailFlyer.tsx (route: /email) Routes: server/src/routes/email.ts (proxied from Next.js) Renderer: @dexian/renderers/email (MJML → HTML)

Stages

brief  →  copy  →  layout  →  html
Stage What it does Engine
brief Parses the user prompt into an EmailBrief (purpose, tone, audience, key messages, CTA, stats, collaborators) Azure OpenAI
copy Generates section-by-section copy (subject line, preheader, headline, body paragraphs, CTA copy) Azure OpenAI
layout Selects an MJML layout template and maps copy into slots Template engine + LLM slot-fill
html Renders MJML → cross-client HTML; final asset is sent or downloaded @dexian/renderers/email (MJML)

The full status machine has 9 states (queued → 4 in-flight + 4 done states + error). See EmailPipelineStatus in server/src/pipeline/emailTypes.ts.

Inputs

type EmailPrompt = {
  purpose: string;            // "Launch announcement for X"
  tone: 'confident' | 'energetic' | 'calm' | 'innovative';
  keyMessages: string[];
  cta: { label: string; url: string };
  audience?: string;
  stats?: { label: string; value: string }[];
  collaborators?: string[];   // names/roles for sign-off
  layoutTemplate?: string;    // override automatic template selection
};

Endpoints

Method Path Purpose
POST /api/email Start the pipeline; returns runId
GET /api/email/:runId/status Poll status (also streamed via SSE)
GET /api/email/:runId Get the full EmailRun (brief + copy + layout)
POST /api/email/:runId/regenerate/:section Regenerate one section (brief, copy, layout, or a sub-slot like headline, cta)
POST /api/email/:runId/render-html Re-render MJML → HTML after edits
POST /api/email/:runId/send-preview Send the rendered HTML to a preview address via Azure Communication Services

Sectional regeneration

The defining UX feature: every section can be regenerated independently without redoing the whole brief. The flow is backed by a backup/revert system — each edit snapshots the prior state so users can compare and revert.

sequenceDiagram
    User->>UI: Edit headline → "Regenerate"
    UI->>API: POST /api/email/:id/regenerate/headline
    API->>LLM: Re-prompt with current brief + retained sections
    LLM-->>API: New headline
    API->>UI: Updated EmailRun (snapshot stored)
    User->>UI: Don't like it → "Revert"
    UI->>API: POST /api/email/:id/revert
    API->>UI: Restored prior snapshot

UI (EmailFlyer.tsx)

  • Live preview — kinetic text animation while the LLM streams new copy
  • Side panel — section list with per-section regenerate buttons
  • Layout switcher — try alternative MJML templates without losing copy
  • Send preview — input an email address; ACS sends a real test email

Render output

The final HTML is stored under runs/<runId>/email.html alongside the JSON run state. It is also returned inline from /render-html so the frontend can download it directly.

Voice in emails

Some templates include a "voiceover" section — short narration text that can be synthesized via the same @dexian/voice Azure TTS path and embedded as an <audio> link in the email.

Future work

See docs/MASTER-PLAN.md Layer-2 templates section — additional MJML layouts (event invite, product update, monthly digest) are planned but not yet shipped.