package email
import (
"fmt"
"html/template"
"net/http"
"strings"
"github.com/go-chi/chi/v5"
)
// DevHandler serves live HTML previews of email templates.
// It is ONLY safe to mount in development environments — never in production.
type DevHandler struct {
renderer *Renderer
}
// NewDevHandler creates a new DevHandler backed by the given renderer.
func NewDevHandler(r *Renderer) *DevHandler {
return &DevHandler{renderer: r}
}
// List serves an HTML page listing all available email template previews.
// Mount at GET /dev/emails.
func (h *DevHandler) List(w http.ResponseWriter, r *http.Request) {
purposes := h.renderer.Purposes()
var sb strings.Builder
sb.WriteString(`
Email Templates — Dev Preview
Email Templates
Development preview — not visible in production.
`)
for _, p := range purposes {
sb.WriteString(fmt.Sprintf(
` - preview%s
`,
template.HTMLEscapeString(p),
template.HTMLEscapeString(p),
))
sb.WriteByte('\n')
}
sb.WriteString(`
`)
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusOK)
_, _ = fmt.Fprint(w, sb.String())
}
// Preview renders an email template with placeholder data and serves the HTML.
// Mount at GET /dev/emails/{purpose}.
func (h *DevHandler) Preview(w http.ResponseWriter, r *http.Request) {
purpose := chi.URLParam(r, "purpose")
ctx := placeholderContext(purpose)
rendered, err := h.renderer.Render(purpose, ctx)
if err != nil {
http.Error(w, fmt.Sprintf("render %q: %v", purpose, err), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusOK)
_, _ = fmt.Fprint(w, rendered.HTML)
}
// placeholderContext returns realistic placeholder data for each email type.
// These values are used by the dev preview only — never sent to real users.
func placeholderContext(purpose string) EmailContext {
switch purpose {
case "login_otp":
return EmailContext{
Code: "482916",
ExpiresIn: 10,
Purpose: "sign in",
}
case "magic_link":
return EmailContext{
ActionURL: "https://example.com/auth/verify?token=preview-token",
ButtonText: "Sign In \u2192",
ExpiresIn: 15,
}
case "password_reset":
return EmailContext{
ActionURL: "https://example.com/auth/reset?token=preview-token",
ButtonText: "Reset Password \u2192",
ExpiresIn: 60,
}
case "verify_email":
return EmailContext{
Code: "738201",
ExpiresIn: 30,
Purpose: "verify your email",
}
case "welcome":
return EmailContext{
ActionURL: "https://example.com/dashboard",
ButtonText: "Get Started \u2192",
Name: "Jordan",
}
default:
return EmailContext{}
}
}