Headerless drag & drop email template builder (React + TypeScript). Canvas with 8+ components, inspector, MJML/HTML/JSON export. Also published as the mailyon npm package.
- Stack: React 18, TypeScript (strict), Tailwind CSS, zustand (state), react-dnd (drag & drop), mjml-browser, CRA (react-scripts 5).
- Entry points:
src/index.tsx(dev app) ·src/components/EmailTemplateBuilder.tsx(main library component) ·src/index.package.ts(npm package exports). - Template model:
EmailTemplate→components: EmailComponent[](nested,columns/columnchildren). All types insrc/types/index.ts. - Deploys to GitHub Pages on push to
mainvia.github/workflows/deploy.yml.
npm start— dev server (CRA)npm run build— production build →build/(verified working)npm run build:package— library build viatsconfig.package.json→dist/(types + commonjs)npm run build:docs— build, then movebuild/→docs/(Pages artifact)npm test— react-scripts test (Jest). No tests exist yet; useCI=true npm testnon-interactivelynpm run deploy— gh-pages deploy (runspredeploybuild first)
src/store/builderStore.ts— single source of truth (zustand): template, selection, hover, undo/redo history, all component CRUD +createComponenthelper.src/components/Builder/— the canvas:Builder.tsx(tabs: editor/pc/mobile),DropZone.tsx,DraggableComponent.tsx,ComponentRenderer.tsx,Preview.tsx.src/components/ComponentLibrary/— left palette of draggable component types.src/components/Inspector/— right panel: per-component props, template settings, code/export tabs.src/components/Toolbar/Toolbar.tsx— top bar: undo/redo, save/load, view toggles.src/hooks/— package-facing wrappers over the store:useEmailTemplateBuilder,useEmailExport,useEmailTemplateManager.src/utils/mjmlExport.ts—generateMJML(template)+convertMJMLToHTML(mjml-browser); must stay in sync with preview rendering.src/utils/componentUtils.ts—getDefaultProps(type)per component type.
- Component types are lowercase strings (
'header' | 'text' | 'image' | 'button' | 'divider' | 'socialMedia' | 'footer' | 'spacer' | 'columns' | 'column'); add new types toComponentTypeinsrc/types/index.ts. - Components are
export const X: React.FC = () => ...named exports. Quoting is inconsistent (double quotes in most components, single in store/hooks/utils) — match the surrounding file. - Every mutating store action deep-clones the template first (
cloneTemplate), then pushes the new snapshot ontohistory(capped at 100) and setsmetadata.updatedAt. Never mutate the current template or history snapshots in place. - Selection is stored as
selectedComponentId(id), not an object reference; resolve it with theselectSelectedComponentselector so it survives undo/redo and template replacement. - Per-component props use
xxxVisibleboolean flags and camelCase sizing (200pxstrings); default props live incomponentUtils.ts. - New public API must be exported from
src/index.package.ts(package build) —src/index.tsxis only the dev shell. mjml-browserhas no types; ambient module declared insrc/types/mjml-browser.d.ts(keep if API changes).- Strict TS (
noEmitfor app, declarations emitted for package).dist/is committed.