Phase 2.5 — close the load/save loop so the editor isn't broken by a page
refresh.
Problem: easy-email doesn't ship an MJML→JSON parser, so loading an
existing MJML template into the editor canvas isn't possible. First-time
load = empty canvas. Without this fix, every page reload would also reset
to empty (even after saving), making the editor useless past one session.
Solution: persist easy-email's raw JSON tree (editor state) as a third
companion file alongside .mjml + .html. Editor reads .json on load when
present, falls back to empty otherwise.
Three files per template now:
gift-email-fr.mjml — MJML source (rendered by send-worker → already done)
gift-email-fr.html — compiled HTML (cached output, sent to recipients)
gift-email-fr.json — easy-email editor state (UI restoration only)
Backend (lib/campaigns.js):
- New templateJsonPath() helper + EDITABLE_TEMPLATES checks
- GET /campaigns/templates/:name returns { format, mjml, html, json }
when format=mjml (json null until first easy-email save)
- PUT /campaigns/templates/:name now accepts body.json alongside body.mjml
(writes both .mjml + .html [compiled] + .json [editor state])
- Backup hook extended to also backup .json before overwrite
Editor (EmailEditorApp.tsx):
- On load: prefer data.json → parse and seed initialValues. If json
missing but mjml present, show explanatory error banner + empty canvas
(user reconstructs once; that save fixes future loads).
- On save: send BOTH mjml (compiled via JsonToMjml) AND raw values
object as json. Hub persists all three artifacts.
First UX flow on next user visit:
1. Open editor → empty canvas + banner "MJML exists but no JSON
editor-state yet; reconstruct once to save a JSON snapshot"
2. User drag-drops blocks to rebuild the template visually
3. Click save → MJML + HTML + JSON all persist
4. Subsequent reloads load from JSON instantly with full editor state
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|---|---|---|
| .. | ||
| src | ||
| .gitignore | ||
| docker-compose.yml | ||
| Dockerfile | ||
| index.html | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
| vite.config.ts | ||
TARGO Email Editor
Standalone email template editor microservice — React + Vite + easy-email (OSS WYSIWYG email builder, MJML-based).
Embedded as an iframe in the ops UI's /campaigns/templates/:name page.
Talks to the hub's /campaigns/templates/* REST endpoints for load/save.
Architecture
ops UI (Vue) → iframe → editor.gigafibre.ca → REST → msg.gigafibre.ca (hub)
└─ writes .mjml + .html
to /opt/targo-hub/templates/
URL params
?name=gift-email-fr— which template to load (defaults to gift-email-fr)
postMessage protocol (to parent window)
Emitted on save success:
{ type: 'email-editor:saved', template: 'gift-email-fr', ts: 1700000000000 }
The parent ops UI listens via:
window.addEventListener('message', (e) => {
if (e.data.type === 'email-editor:saved') {
// refresh preview / show toast
}
})
Local dev
npm install
npm run dev # http://localhost:5173?name=gift-email-fr
Set VITE_HUB_URL env to point at a non-prod hub if needed.
Build + deploy
docker compose build
docker compose up -d
Traefik auto-provisions HTTPS at editor.gigafibre.ca (Let's Encrypt via
the letsencrypt resolver shared with the rest of our stack).
Known limitations (Phase 1)
- Existing MJML templates from the hub are NOT auto-imported into the easy-email JSON tree (no MJML → JSON parser in easy-email out of the box). The editor starts from an empty page. User rebuilds visually with the hub's compiled HTML as visual reference.
- TODO Phase 3: integrate an MJML → easy-email-JSON parser (likely fork or reverse-engineer JsonToMjml).