Skip to content
Back to skills

Elon Email Templates

ASecurity

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.

  • 666 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added February 8, 2026
developmentjavascripttypescriptgojavabashreacttestingapisecurityperformance

Works with

  • cli
  • api

Security analysis

A100/100

Scanned February 12, 2026

npx -y skills add majiayu000/claude-skill-registry --skill elon-email-templates --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Elon Email Templates?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Elon Email Templates
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/majiayu000-elon-email-templates/badge)](https://www.skillsdirectory.com/skills/majiayu000-elon-email-templates)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: elon-email-templates
description: 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.
---

# 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

| File | Purpose |
|------|---------|
| `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:

```typescript
// 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 |
|----------|---------|---------|
| `wrapInBaseTemplate()` | Header + content + footer wrapper | See 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`:

```typescript
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 |
|-------------|---------|-----------------|
| `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

```typescript
// 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`:

```typescript
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

```typescript
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:

```bash
pnpm email:preview
```

## Security Requirements

### XSS Protection

**ALWAYS** use `escapeHtml()` on user-controlled content:

```typescript
// 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`:

```typescript
// 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:
```bash
pnpm tsx scripts/generate-email-logo.ts
```

## Testing

### Visual Previews

Open `.email-previews/index.html` to browse all templates visually.

### Unit Tests

```bash
pnpm test tests/unit/email/
```

### Integration Tests

```bash
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 | `#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

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.