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.md
source 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

react-email
skill documents React Email patterns. This project uses a different approach optimized for simplicity and performance. Follow this skill for Elon AI email work.

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

FilePurpose
lib/jobs/handlers/email-send.ts
All 12 email templates + handler (916 lines)
lib/email/constants.ts
Centralized colors, styles, logo URL
lib/email/unsubscribe-token.ts
HMAC-signed unsubscribe tokens
app/api/email/unsubscribe/route.ts
Unsubscribe API endpoint
lib/jobs/types.ts
EmailSendPayloadSchema
definition
.email-previews/
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

FunctionPurposeExample
wrapInBaseTemplate()
Header + content + footer wrapperSee above
createButton()
Primary/secondary CTA buttons
createButton("Click Me", url, "primary")
createInfoBox()
Key-value info cards
createInfoBox([{label: "Name", value: "John"}])
escapeHtml()
XSS protection for user content
escapeHtml(user.name)

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 IDPurposeKey Data Fields
welcome
New student onboarding
name
role_change
User role changed
userName
,
oldRole
,
newRole
assistant_published
Teacher's assistant goes live
assistantName
,
courseName
,
joinCode
budget_warning
Admin usage alert
percentUsed
,
currentSpend
,
budgetLimit
processing_failed
File upload error
fileName
,
errorMessage
new_student_joined
Admin notification
studentName
,
studentEmail
,
joinedAt
role_request
Admin action needed
userName
,
userEmail
,
requestedRole
role_approved
Role request approved
name
,
newRole
,
tips[]
,
dashboardUrl
role_denied
Role request denied
name
,
requestedRole
,
reason
teacher_digest
Weekly analytics
totalSessions
,
uniqueStudents
,
satisfactionRate
,
topTopic
qr_join_code
QR code invitation
assistantName
,
courseName
,
joinCode
,
qrCodeUrl

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
    tenantId
    in all email payloads
  • All sends are logged to
    audit_logs
    table
  • 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

ColorHexUsage
Maroon
#73000a
Headers, buttons, primary text
Gold
#b59a57
Accents, pro tips, highlights
White
#ffffff
Body backgrounds, button text
Gray-50
#f9fafb
Footer, info box backgrounds
Gray-700
#374151
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