xiaohongshu
xiaohongshu
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.
Full skill instructions
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-emailskill documents React Email patterns. This project uses a different approach optimized for simplicity and performance. Follow this skill for Elon AI email work.
email-send.tsAPI/Action → publishJob({jobType: "email:send"})
→ Upstash QStash (queue)
→ emailSendHandler()
→ Template selection (EMAIL_TEMPLATES[templateId])
→ HTML string generation
→ Resend API
→ User inbox
| 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 |
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.",
}),
}),
| 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) |
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
};
| 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 |
// 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",
}),
}),
};
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
});
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",
},
},
});
Add test data and regenerate previews:
pnpm email:preview
ALWAYS use escapeHtml() on user-controlled content:
// CORRECT - escaped
<p>${escapeHtml(data.userName)}</p>
// WRONG - XSS vulnerability!
<p>${data.userName}</p>
tenantId in all email payloadsaudit_logs tableThe 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
Open .email-previews/index.html to browse all templates visually.
pnpm test tests/unit/email/
pnpm test tests/integration/api/unsubscribe.test.ts
When RESEND_API_KEY is not set, emails are logged to console instead of sent.
| 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 |
The teacher_digest template demonstrates advanced patterns:
The qr_join_code template shows how to include images:
xiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience