gigafibre-fsm/services/email-editor
louispaulb bb88a27b90 feat(email-editor): persist easy-email JSON state for instant restore on reload
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>
2026-05-22 06:04:48 -04:00
..
src feat(email-editor): persist easy-email JSON state for instant restore on reload 2026-05-22 06:04:48 -04:00
.gitignore feat(email-editor): Phase 1 — scaffold easy-email microservice for visual template editing 2026-05-21 23:52:31 -04:00
docker-compose.yml feat(email-editor): Phase 1 — scaffold easy-email microservice for visual template editing 2026-05-21 23:52:31 -04:00
Dockerfile feat(email-editor): Phase 1 — scaffold easy-email microservice for visual template editing 2026-05-21 23:52:31 -04:00
index.html feat(email-editor): Phase 1 — scaffold easy-email microservice for visual template editing 2026-05-21 23:52:31 -04:00
package-lock.json feat(email-editor): Phase 1 — scaffold easy-email microservice for visual template editing 2026-05-21 23:52:31 -04:00
package.json feat(email-editor): Phase 1 — scaffold easy-email microservice for visual template editing 2026-05-21 23:52:31 -04:00
README.md feat(email-editor): Phase 1 — scaffold easy-email microservice for visual template editing 2026-05-21 23:52:31 -04:00
tsconfig.json feat(email-editor): Phase 1 — scaffold easy-email microservice for visual template editing 2026-05-21 23:52:31 -04:00
vite.config.ts feat(email-editor): Phase 1 — scaffold easy-email microservice for visual template editing 2026-05-21 23:52:31 -04:00

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).