# Email Template Generator Implementation Plan

## Overview

Create an email template system that allows both platform admins and business users to create reusable email templates. Templates can be attached to plugins via a new template variable type that prompts users to select from their email templates.

## Requirements Summary

1. **Dual access**: Available in both platform admin and business areas
2. **Both formats**: Support text and HTML email templates
3. **Visual preview**: Show how the final email will look
4. **Plugin integration**: Templates attachable via a new template tag type
5. **Separate templates**: Platform and business templates are completely separate
6. **Footer enforcement**: Free tier businesses must show "Powered by Smooth Schedule" footer (non-overridable)
7. **Editor modes**: Both visual builder AND code editor views

---

## Phase 1: Backend - EmailTemplate Model

### 1.1 Create EmailTemplate Model

**Location**: `schedule/models.py`

```python
class EmailTemplate(models.Model):
    """
    Reusable email template for plugins and automations.

    Supports both text and HTML content with template variable substitution.
    """

    class Scope(models.TextChoices):
        BUSINESS = 'BUSINESS', 'Business'  # Tenant-specific
        PLATFORM = 'PLATFORM', 'Platform'  # Platform-wide (shared)

    name = models.CharField(max_length=200)
    description = models.TextField(blank=True)

    # Email structure
    subject = models.CharField(max_length=500, help_text="Email subject line - supports template variables")
    html_content = models.TextField(blank=True, help_text="HTML email body")
    text_content = models.TextField(blank=True, help_text="Plain text email body")

    # Scope
    scope = models.CharField(
        max_length=20,
        choices=Scope.choices,
        default=Scope.BUSINESS,
    )

    # Only for PLATFORM scope templates
    is_default = models.BooleanField(default=False, help_text="Default template for certain triggers")

    # Metadata
    created_by = models.ForeignKey(
        'users.User',
        on_delete=models.SET_NULL,
        null=True,
        related_name='created_email_templates'
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    # Category for organization
    category = models.CharField(
        max_length=50,
        choices=[
            ('APPOINTMENT', 'Appointment'),
            ('REMINDER', 'Reminder'),
            ('CONFIRMATION', 'Confirmation'),
            ('MARKETING', 'Marketing'),
            ('NOTIFICATION', 'Notification'),
            ('REPORT', 'Report'),
            ('OTHER', 'Other'),
        ],
        default='OTHER'
    )

    # Preview data for visual preview
    preview_context = models.JSONField(
        default=dict,
        blank=True,
        help_text="Sample data for rendering preview"
    )

    class Meta:
        ordering = ['name']
        indexes = [
            models.Index(fields=['scope', 'category']),
        ]

    def __str__(self):
        return f"{self.name} ({self.get_scope_display()})"

    def render(self, context: dict, force_footer: bool = False) -> tuple[str, str, str]:
        """
        Render the template with given context.

        Args:
            context: Dictionary of template variables
            force_footer: If True, append "Powered by Smooth Schedule" footer

        Returns:
            Tuple of (subject, html_content, text_content)
        """
        from .template_parser import TemplateVariableParser

        subject = TemplateVariableParser.replace_insertion_codes(
            self.subject, context
        )
        html = TemplateVariableParser.replace_insertion_codes(
            self.html_content, context
        ) if self.html_content else ''
        text = TemplateVariableParser.replace_insertion_codes(
            self.text_content, context
        ) if self.text_content else ''

        # Append footer for free tier if applicable
        if force_footer:
            html = self._append_html_footer(html)
            text = self._append_text_footer(text)

        return subject, html, text

    def _append_html_footer(self, html: str) -> str:
        """Append Powered by Smooth Schedule footer to HTML"""
        footer = '''
        <div style="margin-top: 40px; padding-top: 20px; border-top: 1px solid #e5e7eb; text-align: center; color: #9ca3af; font-size: 12px;">
            <p>
                Powered by
                <a href="https://smoothschedule.com" style="color: #6366f1; text-decoration: none; font-weight: 500;">
                    SmoothSchedule
                </a>
            </p>
        </div>
        '''
        # Insert before </body> if present, otherwise append
        if '</body>' in html.lower():
            import re
            return re.sub(r'(</body>)', footer + r'\1', html, flags=re.IGNORECASE)
        return html + footer

    def _append_text_footer(self, text: str) -> str:
        """Append Powered by Smooth Schedule footer to plain text"""
        footer = "\n\n---\nPowered by SmoothSchedule - https://smoothschedule.com"
        return text + footer
```

### 1.2 Update TemplateVariableParser

**Location**: `schedule/template_parser.py`

Add new variable type `email_template`:

```python
# Add to VARIABLE_PATTERN handling
# Format: {{PROMPT:variable_name|description||email_template}}

# When type == 'email_template', the frontend will:
# 1. Fetch available email templates from /api/email-templates/
# 2. Show a dropdown selector
# 3. Store the selected template ID in config_values
```

### 1.3 Create Serializers

**Location**: `schedule/serializers.py`

```python
class EmailTemplateSerializer(serializers.ModelSerializer):
    """Full serializer for CRUD operations"""
    created_by_name = serializers.CharField(source='created_by.full_name', read_only=True)

    class Meta:
        model = EmailTemplate
        fields = [
            'id', 'name', 'description', 'subject',
            'html_content', 'text_content', 'scope',
            'is_default', 'category', 'preview_context',
            'created_by', 'created_by_name',
            'created_at', 'updated_at',
        ]
        read_only_fields = ['created_at', 'updated_at', 'created_by']


class EmailTemplateListSerializer(serializers.ModelSerializer):
    """Lightweight serializer for dropdowns"""

    class Meta:
        model = EmailTemplate
        fields = ['id', 'name', 'description', 'category', 'scope']


class EmailTemplatePreviewSerializer(serializers.Serializer):
    """Serializer for preview endpoint"""
    subject = serializers.CharField()
    html_content = serializers.CharField(allow_blank=True)
    text_content = serializers.CharField(allow_blank=True)
    context = serializers.DictField(required=False, default=dict)
```

### 1.4 Create ViewSet

**Location**: `schedule/views.py`

```python
class EmailTemplateViewSet(viewsets.ModelViewSet):
    """
    ViewSet for managing email templates.

    - Business users see only BUSINESS scope templates
    - Platform users can also see/create PLATFORM scope templates
    """
    serializer_class = EmailTemplateSerializer
    permission_classes = [IsAuthenticated]

    def get_queryset(self):
        user = self.request.user

        # Platform users see all templates
        if user.is_platform_user:
            scope = self.request.query_params.get('scope')
            if scope:
                return EmailTemplate.objects.filter(scope=scope.upper())
            return EmailTemplate.objects.all()

        # Business users only see BUSINESS scope templates
        return EmailTemplate.objects.filter(scope=EmailTemplate.Scope.BUSINESS)

    def perform_create(self, serializer):
        serializer.save(created_by=self.request.user)

    @action(detail=False, methods=['post'])
    def preview(self, request):
        """Render a preview of the template with sample data"""
        serializer = EmailTemplatePreviewSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        from .template_parser import TemplateVariableParser

        context = serializer.validated_data.get('context', {})
        subject = serializer.validated_data['subject']
        html = serializer.validated_data.get('html_content', '')
        text = serializer.validated_data.get('text_content', '')

        # Add default sample values
        default_context = {
            'BUSINESS_NAME': 'Demo Business',
            'BUSINESS_EMAIL': 'contact@demo.com',
            'BUSINESS_PHONE': '(555) 123-4567',
            'CUSTOMER_NAME': 'John Doe',
            'CUSTOMER_EMAIL': 'john@example.com',
            'APPOINTMENT_TIME': 'Monday, January 15, 2025 at 2:00 PM',
            'APPOINTMENT_DATE': 'January 15, 2025',
            'APPOINTMENT_SERVICE': 'Consultation',
            'TODAY': datetime.now().strftime('%B %d, %Y'),
            'NOW': datetime.now().strftime('%B %d, %Y at %I:%M %p'),
        }
        default_context.update(context)

        rendered_subject = TemplateVariableParser.replace_insertion_codes(subject, default_context)
        rendered_html = TemplateVariableParser.replace_insertion_codes(html, default_context)
        rendered_text = TemplateVariableParser.replace_insertion_codes(text, default_context)

        # Check if free tier - append footer
        force_footer = False
        if not request.user.is_platform_user:
            from django.db import connection
            if hasattr(connection, 'tenant') and connection.tenant.subscription_tier == 'FREE':
                force_footer = True

        if force_footer:
            rendered_html = EmailTemplate._append_html_footer(None, rendered_html)
            rendered_text = EmailTemplate._append_text_footer(None, rendered_text)

        return Response({
            'subject': rendered_subject,
            'html_content': rendered_html,
            'text_content': rendered_text,
            'force_footer': force_footer,
        })

    @action(detail=True, methods=['post'])
    def duplicate(self, request, pk=None):
        """Create a copy of an existing template"""
        template = self.get_object()
        new_template = EmailTemplate.objects.create(
            name=f"{template.name} (Copy)",
            description=template.description,
            subject=template.subject,
            html_content=template.html_content,
            text_content=template.text_content,
            scope=template.scope,
            category=template.category,
            preview_context=template.preview_context,
            created_by=request.user,
        )
        return Response(EmailTemplateSerializer(new_template).data, status=201)
```

---

## Phase 2: Plugin Integration

### 2.1 Update Template Parser for email_template Type

**Location**: `schedule/template_parser.py`

```python
# In extract_variables method, when type == 'email_template':
# Return special metadata to indicate dropdown

@classmethod
def _infer_type(cls, var_name: str, description: str) -> str:
    # ... existing logic ...

    # Check for explicit email_template type
    # This is handled in the main extraction logic
    pass
```

### 2.2 Update Plugin Execution to Handle Email Templates

**Location**: `schedule/tasks.py`

```python
def execute_plugin_with_email(plugin_code: str, config_values: dict, context: dict):
    """
    Execute plugin code with email template support.

    When config_values contains an email_template_id, load and render it.
    """
    # Check for email template references in config
    for key, value in config_values.items():
        if key.endswith('_email_template') and isinstance(value, int):
            # Load the email template
            try:
                template = EmailTemplate.objects.get(id=value)
                # Render and make available in context
                subject, html, text = template.render(context)
                context[f'{key}_subject'] = subject
                context[f'{key}_html'] = html
                context[f'{key}_text'] = text
            except EmailTemplate.DoesNotExist:
                pass

    # Continue with normal execution
    # ...
```

---

## Phase 3: Frontend - Email Template Editor

### 3.1 Create EmailTemplateEditor Component

**Location**: `frontend/src/pages/EmailTemplates.tsx`

Main page with:
- List of templates with search/filter
- Create/Edit modal with dual-mode editor
- Preview panel

### 3.2 Create EmailTemplateForm Component

**Location**: `frontend/src/components/EmailTemplateForm.tsx`

Features:
1. **Subject line editor** - Simple text input with variable insertion
2. **Content editor** - Tabbed interface:
   - **Visual mode**: WYSIWYG editor (TipTap or similar)
   - **Code mode**: Monaco/CodeMirror for raw HTML
3. **Plain text editor** - Textarea with variable insertion buttons
4. **Preview panel** - Live rendering of HTML email

### 3.3 Variable Insertion Toolbar

Available variables shown as clickable chips:
- `{{BUSINESS_NAME}}`
- `{{BUSINESS_EMAIL}}`
- `{{CUSTOMER_NAME}}`
- `{{APPOINTMENT_TIME}}`
- etc.

### 3.4 Preview Component

**Location**: `frontend/src/components/EmailPreview.tsx`

- Desktop/mobile toggle
- Light/dark mode preview
- Sample data editor
- Footer preview (shown for free tier)

### 3.5 Update Plugin Config Form

**Location**: `frontend/src/pages/MyPlugins.tsx`

When `variable.type === 'email_template'`:
```tsx
<EmailTemplateSelector
  value={configValues[key]}
  onChange={(templateId) => setConfigValues({ ...configValues, [key]: templateId })}
  scope="BUSINESS"  // or "PLATFORM" for platform admin
/>
```

### 3.6 EmailTemplateSelector Component

**Location**: `frontend/src/components/EmailTemplateSelector.tsx`

- Dropdown showing available templates
- Category filtering
- Quick preview on hover
- "Create New" link

---

## Phase 4: Footer Enforcement

### 4.1 Backend Enforcement

In `EmailTemplate.render()` and preview endpoint:
- Check tenant subscription tier
- If `FREE`, always append footer regardless of template content
- Footer cannot be removed via template editing

### 4.2 Frontend Enforcement

In EmailTemplateForm:
- Show permanent footer preview for free tier
- Disable footer editing for free tier
- Show upgrade prompt: "Upgrade to remove footer"

---

## Phase 5: Platform Admin Features

### 5.1 Platform Email Templates Page

**Location**: `frontend/src/pages/platform/PlatformEmailTemplates.tsx`

- Create/manage PLATFORM scope templates
- Set default templates for system events
- Preview with tenant context

### 5.2 Default Templates

Create seed data for common templates:
- Tenant invitation email (already exists)
- Appointment reminder
- Appointment confirmation
- Password reset
- Welcome email

---

## Database Migrations

```python
# schedule/migrations/XXXX_email_template.py

class Migration(migrations.Migration):
    dependencies = [
        ('schedule', 'previous_migration'),
    ]

    operations = [
        migrations.CreateModel(
            name='EmailTemplate',
            fields=[
                ('id', models.BigAutoField(auto_created=True, primary_key=True)),
                ('name', models.CharField(max_length=200)),
                ('description', models.TextField(blank=True)),
                ('subject', models.CharField(max_length=500)),
                ('html_content', models.TextField(blank=True)),
                ('text_content', models.TextField(blank=True)),
                ('scope', models.CharField(max_length=20, choices=[
                    ('BUSINESS', 'Business'),
                    ('PLATFORM', 'Platform'),
                ], default='BUSINESS')),
                ('is_default', models.BooleanField(default=False)),
                ('category', models.CharField(max_length=50, choices=[
                    ('APPOINTMENT', 'Appointment'),
                    ('REMINDER', 'Reminder'),
                    ('CONFIRMATION', 'Confirmation'),
                    ('MARKETING', 'Marketing'),
                    ('NOTIFICATION', 'Notification'),
                    ('REPORT', 'Report'),
                    ('OTHER', 'Other'),
                ], default='OTHER')),
                ('preview_context', models.JSONField(default=dict, blank=True)),
                ('created_at', models.DateTimeField(auto_now_add=True)),
                ('updated_at', models.DateTimeField(auto_now=True)),
                ('created_by', models.ForeignKey(
                    null=True,
                    on_delete=django.db.models.deletion.SET_NULL,
                    related_name='created_email_templates',
                    to='users.user'
                )),
            ],
            options={
                'ordering': ['name'],
            },
        ),
        migrations.AddIndex(
            model_name='emailtemplate',
            index=models.Index(fields=['scope', 'category'], name='schedule_em_scope_123456_idx'),
        ),
    ]
```

---

## API Endpoints

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/email-templates/` | List templates (filtered by scope) |
| POST | `/api/email-templates/` | Create template |
| GET | `/api/email-templates/{id}/` | Get template details |
| PATCH | `/api/email-templates/{id}/` | Update template |
| DELETE | `/api/email-templates/{id}/` | Delete template |
| POST | `/api/email-templates/preview/` | Render preview |
| POST | `/api/email-templates/{id}/duplicate/` | Duplicate template |

---

## Frontend Routes

| Route | Component | Description |
|-------|-----------|-------------|
| `/email-templates` | EmailTemplates | Business email templates |
| `/email-templates/create` | EmailTemplateForm | Create new template |
| `/email-templates/:id/edit` | EmailTemplateForm | Edit existing template |
| `/platform/email-templates` | PlatformEmailTemplates | Platform templates |

---

## Implementation Order

1. **Backend Model & Migration** - EmailTemplate model
2. **Backend API** - Serializers, ViewSet, URLs
3. **Frontend List Page** - Basic CRUD
4. **Frontend Editor** - Dual-mode editor
5. **Preview Component** - Live preview
6. **Plugin Integration** - email_template variable type
7. **Footer Enforcement** - Free tier logic
8. **Platform Admin** - Platform templates page
9. **Seed Data** - Default templates

---

## Testing Checklist

- [ ] Create business email template
- [ ] Create platform email template (as superuser)
- [ ] Preview template with sample data
- [ ] Verify footer appears for free tier
- [ ] Verify footer hidden for paid tiers
- [ ] Verify footer appears in preview for free tier
- [ ] Edit template in visual mode
- [ ] Edit template in code mode
- [ ] Use template in plugin configuration
- [ ] Send actual email using template
- [ ] Duplicate template
- [ ] Delete template
