Fix email unconfirmed login flow with OTP resend and update email API field names
This commit is contained in:
358
EMAIL-TEMPLATE-SYSTEM.md
Normal file
358
EMAIL-TEMPLATE-SYSTEM.md
Normal file
@@ -0,0 +1,358 @@
|
||||
# Unified Email Template System
|
||||
|
||||
## Overview
|
||||
|
||||
All emails now use a **single master template** for consistent branding and design. The master template wraps content-only HTML from database templates.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Database Template (content only)
|
||||
↓
|
||||
Process Shortcodes ({nama}, {platform_name}, etc.)
|
||||
↓
|
||||
EmailTemplateRenderer.render() - wraps with master template
|
||||
↓
|
||||
Complete HTML Email sent via provider
|
||||
```
|
||||
|
||||
## Master Template
|
||||
|
||||
**Location:** `supabase/shared/email-template-renderer.ts`
|
||||
|
||||
**Features:**
|
||||
- Brutalist design (black borders, hard shadows)
|
||||
- Responsive layout (600px max width)
|
||||
- `.tiptap-content` wrapper for auto-styling
|
||||
- Header with brand name + notification ID
|
||||
- Footer with unsubscribe links
|
||||
- All CSS included (no external dependencies)
|
||||
|
||||
**CSS Classes for Content:**
|
||||
- `.tiptap-content h1, h2, h3` - Headings
|
||||
- `.tiptap-content p` - Paragraphs
|
||||
- `.tiptap-content a` - Links (underlined, bold)
|
||||
- `.tiptap-content ul, ol` - Lists
|
||||
- `.tiptap-content table` - Tables with brutalist borders
|
||||
- `.btn` - Buttons with hard shadow
|
||||
- `.otp-box` - OTP codes with dashed border
|
||||
- `.alert-success, .alert-danger, .alert-info` - Colored alert boxes
|
||||
|
||||
## Database Templates
|
||||
|
||||
### Format
|
||||
|
||||
**CORRECT** (content-only):
|
||||
```html
|
||||
<h1>Payment Successful!</h1>
|
||||
<p>Hello <strong>{nama}</strong>, your payment has been confirmed.</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Parameter</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Order ID</td>
|
||||
<td>{order_id}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
```
|
||||
|
||||
**WRONG** (full HTML):
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>...</head>
|
||||
<body>
|
||||
<h1>Payment Successful!</h1>
|
||||
...
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### Why Content-Only?
|
||||
|
||||
The master template provides:
|
||||
- Email client compatibility (resets, Outlook fixes)
|
||||
- Consistent header/footer
|
||||
- Responsive wrapper
|
||||
- Brutalist styling
|
||||
|
||||
Your content just needs the **body HTML** - no `<html>`, `<head>`, or `<body>` tags.
|
||||
|
||||
## Usage in Edge Functions
|
||||
|
||||
### Auth OTP (`send-auth-otp`)
|
||||
|
||||
```typescript
|
||||
import { EmailTemplateRenderer } from "../shared/email-template-renderer.ts";
|
||||
|
||||
// Fetch template from database
|
||||
const template = await supabase
|
||||
.from("notification_templates")
|
||||
.select("*")
|
||||
.eq("key", "auth_email_verification")
|
||||
.single();
|
||||
|
||||
// Process shortcodes
|
||||
let htmlContent = template.email_body_html;
|
||||
Object.entries(templateVars).forEach(([key, value]) => {
|
||||
htmlContent = htmlContent.replace(new RegExp(`{${key}}`, 'g'), value);
|
||||
});
|
||||
|
||||
// Wrap with master template
|
||||
const htmlBody = EmailTemplateRenderer.render({
|
||||
subject: template.email_subject,
|
||||
content: htmlContent,
|
||||
brandName: settings.platform_name || 'ACCESS HUB',
|
||||
});
|
||||
|
||||
// Send via send-email-v2
|
||||
await fetch(`${supabaseUrl}/functions/v1/send-email-v2`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
to: email,
|
||||
html_body: htmlBody,
|
||||
// ... other fields
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
### Other Notifications (`send-notification`)
|
||||
|
||||
```typescript
|
||||
import { EmailTemplateRenderer } from "../shared/email-template-renderer.ts";
|
||||
|
||||
// Fetch template and process shortcodes
|
||||
const htmlContent = replaceVariables(template.body_html || template.body_text, allVariables);
|
||||
|
||||
// Wrap with master template
|
||||
const htmlBody = EmailTemplateRenderer.render({
|
||||
subject: subject,
|
||||
content: htmlContent,
|
||||
brandName: settings.brand_name || "ACCESS HUB",
|
||||
});
|
||||
|
||||
// Send via provider (SMTP, Resend, etc.)
|
||||
await sendViaSMTP({ html: htmlBody, ... });
|
||||
```
|
||||
|
||||
## Available Shortcodes
|
||||
|
||||
See `ShortcodeProcessor.DEFAULT_DATA` in `supabase/shared/email-template-renderer.ts`:
|
||||
|
||||
**User:**
|
||||
- `{nama}` - User name
|
||||
- `{email}` - User email
|
||||
|
||||
**Order:**
|
||||
- `{order_id}` - Order ID
|
||||
- `{tanggal_pesanan}` - Order date
|
||||
- `{total}` - Total amount
|
||||
- `{metode_pembayaran}` - Payment method
|
||||
|
||||
**Product:**
|
||||
- `{produk}` - Product name
|
||||
- `{kategori_produk}` - Product category
|
||||
|
||||
**Access:**
|
||||
- `{link_akses}` - Access link
|
||||
- `{username_akses}` - Access username
|
||||
- `{password_akses}` - Access password
|
||||
|
||||
**Consulting:**
|
||||
- `{tanggal_konsultasi}` - Consultation date
|
||||
- `{jam_konsultasi}` - Consultation time
|
||||
- `{link_meet}` - Meeting link
|
||||
|
||||
**And many more...**
|
||||
|
||||
## Creating New Templates
|
||||
|
||||
### 1. Design Content-Only HTML
|
||||
|
||||
Use brutalist components:
|
||||
|
||||
```html
|
||||
<h1>Welcome!</h1>
|
||||
<p>Hello <strong>{nama}</strong>, welcome to <strong>{platform_name}</strong>!</p>
|
||||
|
||||
<h2>Your Details</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Email</td>
|
||||
<td>{email}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Plan</td>
|
||||
<td>{plan_name}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p style="margin-top: 30px;">
|
||||
<a href="{dashboard_link}" class="btn btn-full">
|
||||
Go to Dashboard
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<blockquote class="alert-success">
|
||||
<strong>Success!</strong> Your account is ready to use.
|
||||
</blockquote>
|
||||
```
|
||||
|
||||
### 2. Add to Database
|
||||
|
||||
```sql
|
||||
INSERT INTO notification_templates (
|
||||
key,
|
||||
name,
|
||||
is_active,
|
||||
email_subject,
|
||||
email_body_html
|
||||
) VALUES (
|
||||
'welcome_email',
|
||||
'Welcome Email',
|
||||
true,
|
||||
'Welcome to {platform_name}!',
|
||||
'---<h1>Welcome!</h1>...---'
|
||||
);
|
||||
```
|
||||
|
||||
### 3. Use in Edge Function
|
||||
|
||||
```typescript
|
||||
const template = await getTemplate('welcome_email');
|
||||
const htmlContent = processShortcodes(template.body_html, {
|
||||
nama: user.name,
|
||||
platform_name: settings.brand_name,
|
||||
email: user.email,
|
||||
plan_name: user.plan,
|
||||
dashboard_link: 'https://...',
|
||||
});
|
||||
|
||||
const htmlBody = EmailTemplateRenderer.render({
|
||||
subject: template.subject,
|
||||
content: htmlContent,
|
||||
brandName: settings.brand_name,
|
||||
});
|
||||
```
|
||||
|
||||
## Migration Notes
|
||||
|
||||
### Old Templates (Self-Contained HTML)
|
||||
|
||||
If you have old templates with full HTML:
|
||||
|
||||
**Before:**
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body { font-family: Arial; }
|
||||
.container { max-width: 600px; }
|
||||
h1 { color: #0066cc; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Welcome!</h1>
|
||||
<p>Hello...</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
**After (Content-Only):**
|
||||
```html
|
||||
<h1>Welcome!</h1>
|
||||
<p>Hello...</p>
|
||||
```
|
||||
|
||||
Remove:
|
||||
- `<!DOCTYPE html>`
|
||||
- `<html>`, `<head>`, `<body>` tags
|
||||
- `<style>` blocks
|
||||
- Container `<div>` wrappers
|
||||
- Header/footer HTML
|
||||
|
||||
Keep:
|
||||
- Content HTML only
|
||||
- Shortcode placeholders `{variable}`
|
||||
- Inline styles for special cases (rare)
|
||||
|
||||
## Benefits
|
||||
|
||||
✅ **Consistent Branding** - All emails have same header/footer
|
||||
✅ **Single Source of Truth** - One master template controls design
|
||||
✅ **Easy Updates** - Change design in one place
|
||||
✅ **Email Client Compatible** - Master template has all the fixes
|
||||
✅ **Less Duplication** - No more reinventing styles per template
|
||||
✅ **Auto-Styling** - `.tiptap-content` CSS makes content look good
|
||||
|
||||
## Files
|
||||
|
||||
- `supabase/shared/email-template-renderer.ts` - Master template + renderer
|
||||
- `supabase/functions/send-auth-otp/index.ts` - Uses master template
|
||||
- `supabase/functions/send-notification/index.ts` - Uses master template
|
||||
- `supabase/migrations/20250102000005_fix_auth_email_template_content_only.sql` - Auth template update
|
||||
- `email-master-template.html` - Visual reference of master template
|
||||
|
||||
## Testing
|
||||
|
||||
### Test Master Template
|
||||
|
||||
```bash
|
||||
# Test with curl
|
||||
curl -X POST https://lovable.backoffice.biz.id/functions/v1/send-auth-otp \
|
||||
-H "Authorization: Bearer YOUR_SERVICE_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"user_id":"TEST_ID","email":"test@example.com"}'
|
||||
```
|
||||
|
||||
### Expected Result
|
||||
|
||||
Email should have:
|
||||
- Black header with "ACCESS HUB" logo
|
||||
- Notification ID (NOTIF #XXXXXX)
|
||||
- Content styled with brutalist design
|
||||
- Gray footer with unsubscribe links
|
||||
- Responsive on mobile
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Email Has No Styling
|
||||
|
||||
**Cause:** Template has full HTML, getting double-wrapped
|
||||
|
||||
**Fix:** Remove `<html>`, `<head>`, `<body>` from database template
|
||||
|
||||
### Styling Not Applied
|
||||
|
||||
**Cause:** Content not in `.tiptap-content` wrapper
|
||||
|
||||
**Fix:** Master template automatically wraps `{{content}}` in `.tiptap-content` div
|
||||
|
||||
### Broken Layout
|
||||
|
||||
**Cause:** Old template has container divs/wrappers
|
||||
|
||||
**Fix:** Remove container divs, keep only content HTML
|
||||
|
||||
---
|
||||
|
||||
**Status:** ✅ Unified system active
|
||||
**Last Updated:** 2025-01-02
|
||||
Reference in New Issue
Block a user