Claude-skill-registry elon-email-templates
Use when creating or modifying email templates for Elon AI - welcome emails, role changes, notifications, teacher digests, or QR code invitations. This project uses HTML string templates, NOT React Email components.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/elon-email-templates" ~/.claude/skills/majiayu000-claude-skill-registry-elon-email-templates && rm -rf "$T"
manifest:
skills/data/elon-email-templates/SKILL.mdsource content
Elon AI Email Templates
Overview
HTML string-based email templates for the Elon AI Classroom Assistant platform. This project does NOT use React Email components - all templates are pure HTML strings with TypeScript helper functions.
Important: The global
skill documents React Email patterns. This project uses a different approach optimized for simplicity and performance. Follow this skill for Elon AI email work.react-email
When to Use
- Creating new transactional email templates
- Modifying existing templates in
email-send.ts - Understanding the email queue architecture
- Adding new email types to the system
Architecture
Email Flow
API/Action → publishJob({jobType: "email:send"}) → Upstash QStash (queue) → emailSendHandler() → Template selection (EMAIL_TEMPLATES[templateId]) → HTML string generation → Resend API → User inbox
Key Files
| File | Purpose |
|---|---|
| All 12 email templates + handler (916 lines) |
| Centralized colors, styles, logo URL |
| HMAC-signed unsubscribe tokens |
| Unsubscribe API endpoint |
| definition |
| HTML preview files for visual testing |
Template Pattern
All templates use pure HTML strings with helper functions:
// Template structure in EMAIL_TEMPLATES object welcome: (data) => ({ subject: "Welcome to Elon AI!", html: wrapInBaseTemplate({ headerTitle: `Welcome, ${escapeHtml(data.name)}!`, headerSubtitle: "Your AI-powered study companion is ready", content: ` <p style="${STYLES.bodyText}"> Hey ${escapeHtml(data.name)}, welcome to Elon AI! </p> ${createButton("Get Started", url)} `, proTip: "Your conversations are FERPA-protected.", }), }),
Helper Functions
| Function | Purpose | Example |
|---|---|---|
| Header + content + footer wrapper | See above |
| Primary/secondary CTA buttons | |
| Key-value info cards | |
| XSS protection for user content | |
Style Constants
All styles are defined in
STYLES object at top of email-send.ts:
const STYLES = { container: "...", // Max-width 600px centered layout header: "...", // Maroon gradient header headerTitle: "...", // White text for header body: "...", // White background body bodyText: "...", // Gray text paragraphs button: "...", // Maroon primary button buttonSecondary: "...", // White bordered button infoBox: "...", // Gray background info card proTipBox: "...", // Gold accent tip box footer: "...", // Gray footer // ... more };
Existing Templates (12)
| Template ID | Purpose | Key Data Fields |
|---|---|---|
| New student onboarding | |
| User role changed | , , |
| Teacher's assistant goes live | , , |
| Admin usage alert | , , |
| File upload error | , |
| Admin notification | , , |
| Admin action needed | , , |
| Role request approved | , , , |
| Role request denied | , , |
| Weekly analytics | , , , |
| QR code invitation | , , , |
Adding a New Template
Step 1: Add to EMAIL_TEMPLATES object
// In lib/jobs/handlers/email-send.ts const EMAIL_TEMPLATES = { // ... existing templates // Your new template my_new_template: (data) => ({ subject: `Your subject with ${escapeHtml(data.dynamicValue)}`, html: wrapInBaseTemplate({ headerTitle: "Header Title", headerSubtitle: "Optional subtitle", content: ` <p style="${STYLES.bodyText}"> ${escapeHtml(data.message)} </p> ${createButton("Action", data.actionUrl)} `, proTip: "Optional helpful tip", }), }), };
Step 2: Add payload schema (if needed)
If your template needs specific data validation, update
lib/jobs/types.ts:
export const EmailSendPayloadSchema = z.object({ tenantId: z.string().uuid(), templateId: z.string(), // Template ID to: z.string().email(), subject: z.string().optional(), // Override template subject data: z.record(z.unknown()), // Template-specific data });
Step 3: Trigger the email
import { publishJob } from "@/lib/jobs/publisher"; await publishJob({ jobType: "email:send", payload: { tenantId: user.tenantId, templateId: "my_new_template", to: user.email, data: { message: "Hello world!", actionUrl: "https://elon-ai.app/action", }, }, });
Step 4: Generate preview
Add test data and regenerate previews:
pnpm email:preview
Security Requirements
XSS Protection
ALWAYS use
escapeHtml() on user-controlled content:
// CORRECT - escaped <p>${escapeHtml(data.userName)}</p> // WRONG - XSS vulnerability! <p>${data.userName}</p>
FERPA Compliance
- Include
in all email payloadstenantId - All sends are logged to
tableaudit_logs - Don't include student PII in email subjects
- Unsubscribe tokens use HMAC-SHA256 signing
Multi-Tenant Scoping
- Emails are always scoped to a tenant
- Job queue handles tenant isolation
- Audit logs record tenant context
Email Client Compatibility
DO
- Use PNG/JPG images with absolute URLs
- Use inline CSS styles (no classes)
- Use table-based layouts for columns
- Keep emails under 102KB
- Test in Gmail, Outlook, Apple Mail
DON'T
- Use SVG images (Gmail strips them)
- Use CSS Grid or Flexbox
- Use media queries (limited support)
- Use external CSS files
- Use JavaScript
Logo
The logo is a hosted PNG at
public/email-assets/elon-ai-logo.png:
// Defined in lib/jobs/handlers/email-send.ts const EMAIL_LOGO_URL = "https://elon-ai.app/email-assets/elon-ai-logo.png"; const LOGO_IMG = `<img src="${EMAIL_LOGO_URL}" alt="Elon AI" width="48" height="48" />`;
To regenerate the logo PNG:
pnpm tsx scripts/generate-email-logo.ts
Testing
Visual Previews
Open
.email-previews/index.html to browse all templates visually.
Unit Tests
pnpm test tests/unit/email/
Integration Tests
pnpm test tests/integration/api/unsubscribe.test.ts
Development Mode
When
RESEND_API_KEY is not set, emails are logged to console instead of sent.
Brand Colors
| Color | Hex | Usage |
|---|---|---|
| Maroon | | Headers, buttons, primary text |
| Gold | | Accents, pro tips, highlights |
| White | | Body backgrounds, button text |
| Gray-50 | | Footer, info box backgrounds |
| Gray-700 | | Body text |
Common Patterns
Teacher Analytics Email
The
teacher_digest template demonstrates advanced patterns:
- Dynamic headlines based on metrics
- Conditional content based on activity level
- Unsubscribe link with HMAC token
- Color-coded metric changes
QR Code Email
The
qr_join_code template shows how to include images:
- Hosted QR code image URL
- Fallback text if image fails
- Clear call-to-action