Skip to main content

Template Builder Implementation Plan

Overview

Implement a visual email template builder using Unlayer's React Email Editor alongside the existing HTML code editor, giving users two options for creating templates.


Phase 1: UI/UX Improvements

1.1 Templates Page - Skeleton Loaders

File: apps/dashboard/src/pages/Templates.tsx

Changes:

  • Add skeleton loader while templates are loading
  • Use Shadcn skeleton component
  • Show 3-5 skeleton cards matching template card layout
  • Replace loading state with skeleton

Components Needed:

  • <Skeleton /> from Shadcn UI

1.2 Template Type Selection Modal

New Component: apps/dashboard/src/components/TemplateTypeModal.tsx

Design:

┌─────────────────────────────────────────┐
│ Choose Template Type │
├─────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 📝 HTML │ │ 🎨 Builder │ │
│ │ │ │ │ │
│ │ Code-based │ │ Visual │ │
│ │ editor │ │ drag-drop │ │
│ │ │ │ │ │
│ │ Advanced │ │ Easy to │ │
│ │ developers │ │ use │ │
│ │ │ │ │ │
│ └──────────────┘ └──────────────┘ │
│ │
│ [Cancel] │
└─────────────────────────────────────────┘

Features:

  • Two large, clickable cards
  • Visual icons for each type
  • Brief description of each option
  • Hover effects
  • Responsive design

Props:

interface TemplateTypeModalProps {
open: boolean;
onClose: () => void;
onSelect: (type: 'HTML' | 'BUILDER') => void;
}

Phase 2: Database Schema Updates

2.1 Update Template Model

File: packages/shared/prisma/schema.prisma

Changes:

model Template {
// ... existing fields

// Add new fields
editorType TemplateEditorType @default(HTML) // 'HTML' or 'BUILDER'
designJson Json? // Unlayer design JSON (only for BUILDER type)

// ... rest of fields
}

enum TemplateEditorType {
HTML
BUILDER
}

Migration:

  • Create migration: pnpm prisma migrate dev --name add_template_editor_type
  • Existing templates default to HTML type

Phase 3: Unlayer Integration

3.1 Install Dependencies

cd apps/dashboard
pnpm add react-email-editor
pnpm add -D @types/react-email-editor

3.2 Create Visual Builder Page

New File: apps/dashboard/src/pages/TemplateBuilderVisual.tsx

Features:

  • Full-screen Unlayer editor
  • Save button to save design + export HTML
  • Back button to return to templates
  • Auto-save draft functionality
  • Template name input
  • Template type selector (TRANSACTIONAL/MARKETING)

Layout:

┌─────────────────────────────────────────────────────┐
│ [← Back] Template Name: [________] [Save] [Test] │
├─────────────────────────────────────────────────────┤
│ │
│ │
│ Unlayer Email Editor │
│ (Full Height) │
│ │
│ │
└─────────────────────────────────────────────────────┘

Code Structure:

const TemplateBuilderVisual = () => {
const emailEditorRef = useRef(null);
const [templateName, setTemplateName] = useState('');
const [templateType, setTemplateType] = useState('TRANSACTIONAL');

// Load existing design if editing
useEffect(() => {
if (templateId && existingDesign) {
emailEditorRef.current?.editor.loadDesign(existingDesign);
}
}, []);

const handleSave = async () => {
emailEditorRef.current?.editor.exportHtml((data) => {
const { design, html } = data;

// Save to API
await apiClient.createTemplate({
name: templateName,
type: templateType,
editorType: 'BUILDER',
htmlBody: html,
designJson: design,
// ... other fields
});
});
};

return (
<div className="h-screen flex flex-col">
<Header />
<EmailEditor
ref={emailEditorRef}
onReady={onEditorReady}
minHeight="calc(100vh - 64px)"
/>
</div>
);
};

Phase 4: Routing Updates

4.1 Update App Routes

File: apps/dashboard/src/App.tsx

Add Routes:

// Add new route for visual builder
<Route
path="templates/new/builder"
element={
<Suspense fallback={<PageLoader />}>
<TemplateBuilderVisual />
</Suspense>
}
/>
<Route
path="templates/:templateId/edit/builder"
element={
<Suspense fallback={<PageLoader />}>
<TemplateBuilderVisual />
</Suspense>
}
/>

// Keep existing HTML editor routes
<Route path="templates/new" element={...} />
<Route path="templates/:templateId/edit" element={...} />

4.2 Update Templates Page Navigation

File: apps/dashboard/src/pages/Templates.tsx

Changes:

const handleCreateTemplate = () => {
setShowTypeModal(true); // Show modal instead of navigating directly
};

const handleTypeSelect = (type: 'HTML' | 'BUILDER') => {
setShowTypeModal(false);
if (type === 'HTML') {
navigate('/templates/new');
} else {
navigate('/templates/new/builder');
}
};

// When editing template, check editorType
const handleEditTemplate = (template) => {
if (template.editorType === 'BUILDER') {
navigate(`/templates/${template.id}/edit/builder`);
} else {
navigate(`/templates/${template.id}/edit`);
}
};

Phase 5: API Updates

5.1 Update Template Controller

File: packages/api/src/api/controllers/template.controller.ts

Changes:

  • Accept editorType in create/update
  • Accept designJson for BUILDER templates
  • Validate designJson is provided for BUILDER type

5.2 Update Template Service

File: packages/api/src/services/template.service.ts

Changes:

interface CreateTemplateInput {
// ... existing fields
editorType?: 'HTML' | 'BUILDER';
designJson?: any; // Unlayer design JSON
}

async function createTemplate(data: CreateTemplateInput) {
// Validate
if (data.editorType === 'BUILDER' && !data.designJson) {
throw new Error('Design JSON required for BUILDER templates');
}

// Create
return prisma.template.create({
data: {
// ... existing fields
editorType: data.editorType || 'HTML',
designJson: data.designJson,
}
});
}

5.3 Update API Client

File: apps/dashboard/src/api/client.ts

Changes:

interface CreateTemplateData {
// ... existing fields
editorType?: 'HTML' | 'BUILDER';
designJson?: any;
}

async createTemplate(data: CreateTemplateData) {
// ... implementation
}

Phase 6: Template Display Updates

6.1 Template Cards - Show Editor Type Badge

File: apps/dashboard/src/pages/Templates.tsx

Changes:

  • Add badge showing "HTML" or "Visual Builder"
  • Different icon for each type
  • Filter by editor type (optional)

UI:

┌──────────────────────────────┐
│ Welcome Email [HTML] │ ← Badge
│ TRANSACTIONAL │
│ Created: 2 days ago │
│ [Edit] [Preview] [Delete] │
└──────────────────────────────┘

┌──────────────────────────────┐
│ Newsletter [🎨 Builder] │ ← Badge
│ MARKETING │
│ Created: 5 days ago │
│ [Edit] [Preview] [Delete] │
└──────────────────────────────┘

6.2 Template Detail Page

File: apps/dashboard/src/pages/TemplateDetail.tsx

Changes:

  • Show editor type
  • For BUILDER templates, show "Edit in Visual Builder" button
  • For HTML templates, show "Edit Code" button

Phase 7: Features & Polish

7.1 Auto-Save Drafts

  • Save design JSON to localStorage every 30 seconds
  • Restore from localStorage if user navigates away
  • Clear localStorage on successful save

7.2 Template Preview

  • For BUILDER templates, export HTML and show preview
  • For HTML templates, show existing preview functionality

7.3 Test Email

  • Export HTML from Unlayer
  • Send test email with exported HTML
  • Show success/error toast

7.4 Unlayer Configuration

Customize Unlayer editor:

const unlayerOptions = {
projectId: YOUR_PROJECT_ID, // Get from Unlayer dashboard
displayMode: 'email',
appearance: {
theme: 'modern_light',
},
features: {
textEditor: {
spellChecker: true,
},
},
locale: 'en-US',
tools: {
// Customize available tools
},
blocks: {
// Custom content blocks
},
};

Implementation Order

Sprint 1: Foundation (Day 1-2)

  1. ✅ Add skeleton loaders to Templates page
  2. ✅ Create TemplateTypeModal component
  3. ✅ Update database schema
  4. ✅ Run migration

Sprint 2: Unlayer Integration (Day 3-4)

  1. ✅ Install Unlayer package
  2. ✅ Create TemplateBuilderVisual page
  3. ✅ Add routing for visual builder
  4. ✅ Basic save/load functionality

Sprint 3: API Integration (Day 5)

  1. ✅ Update API to accept editorType and designJson
  2. ✅ Update frontend API client
  3. ✅ Connect visual builder to API

Sprint 4: Polish & Testing (Day 6-7)

  1. ✅ Add editor type badges to template cards
  2. ✅ Update edit navigation logic
  3. ✅ Test entire flow (create, edit, preview, send)
  4. ✅ Add auto-save functionality
  5. ✅ Documentation

Technical Considerations

1. Design JSON Storage

  • Store Unlayer design JSON in designJson field (JSONB in Postgres)
  • This allows re-editing in visual builder
  • HTML is generated from design JSON on save

2. Editor Type Immutability

  • Once template is created with a type, it cannot be changed
  • Prevents confusion and data loss
  • Show warning if user tries to switch

3. Backward Compatibility

  • All existing templates default to editorType: 'HTML'
  • designJson is nullable
  • HTML templates work exactly as before

4. Performance

  • Lazy load Unlayer editor (code splitting)
  • Only load on builder routes
  • Keep bundle size manageable

5. Data Migration

-- Set all existing templates to HTML type
UPDATE "Template" SET "editorType" = 'HTML' WHERE "editorType" IS NULL;

File Structure

apps/dashboard/src/
├── components/
│ └── TemplateTypeModal.tsx (NEW)
├── pages/
│ ├── Templates.tsx (UPDATED - add skeleton)
│ ├── TemplateBuilderVisual.tsx (NEW)
│ └── TemplateEditor.tsx (EXISTING - no changes)
└── App.tsx (UPDATED - add routes)

packages/api/src/
├── api/
│ └── controllers/
│ └── template.controller.ts (UPDATED)
└── services/
└── template.service.ts (UPDATED)

packages/shared/
└── prisma/
└── schema.prisma (UPDATED)

Testing Checklist

Functional Testing

  • Create HTML template (existing flow still works)
  • Create BUILDER template (new flow)
  • Edit HTML template
  • Edit BUILDER template
  • Cannot edit HTML template in visual builder
  • Cannot edit BUILDER template in code editor
  • Preview both template types
  • Send test email with both types
  • Delete both template types
  • Skeleton loaders show correctly
  • Type selection modal works
  • Navigation between types works

Edge Cases

  • Navigate away without saving (auto-save)
  • Restore from auto-save
  • Large design JSON (performance)
  • Invalid design JSON
  • Template with no content
  • Very long template names

Browser Testing

  • Chrome
  • Firefox
  • Safari
  • Edge

Success Criteria

  1. ✅ Users can choose between HTML and Visual Builder
  2. ✅ Skeleton loaders improve perceived performance
  3. ✅ Visual builder is intuitive and easy to use
  4. ✅ Templates created in visual builder can be re-edited
  5. ✅ HTML templates continue to work as before
  6. ✅ No breaking changes to existing functionality
  7. ✅ Performance remains acceptable
  8. ✅ All tests pass

Future Enhancements

Phase 2 Features (Future)

  • Template preview thumbnails
  • Template cloning (copy design JSON)
  • Template versioning
  • Shared template library
  • Custom Unlayer blocks
  • Template marketplace
  • A/B testing variants
  • Template analytics

Questions to Resolve

  1. Unlayer License: Free tier limitations? Need paid plan?
  2. Project ID: Where to store Unlayer project ID?
  3. Custom Blocks: Do we need custom content blocks initially?
  4. Auto-save: Save to backend or just localStorage?
  5. Template Conversion: Allow converting HTML → Builder? (complex)

Next Steps

  1. Review this plan
  2. Answer questions above
  3. Get approval to proceed
  4. Start Sprint 1 (skeleton loaders + modal)