email-template
SKILL.md
Email Template - Responsive Transactional Emails
Description
Provides responsive transactional email template patterns for NodeJS-Starter-V1 using React Email and Resend. Covers Scientific Luxury dark-theme email design, template composition with shared components, preview tooling, and integration with the project's cron jobs and notification flows using Australian locale formatting.
When to Apply
Positive Triggers
- Creating transactional email templates (welcome, password reset, alerts)
- Building notification emails for cron job reports or agent alerts
- Designing email layouts that match the Scientific Luxury design system
- Setting up email delivery infrastructure (Resend, SMTP, SendGrid)
- Adding email preview and testing tooling
- User mentions: "email", "template", "notification", "transactional", "welcome email"
Negative Triggers
- Building in-app toast notifications (use UI component patterns)
- Sending Slack or webhook notifications (use
webhook-handlerpatterns) - Generating PDF reports (use
pdf-generatorpatterns) - Handling email as CSV import data (use
csv-processorinstead)
Core Directives
The Four Rules of Email Templates
- Table-based layout: Email clients do not support CSS Grid/Flexbox — use
<table>wrappers - Inline styles: Most clients strip
<style>blocks — React Email handles this automatically - Dark theme first: Scientific Luxury OLED aesthetic adapted for email (dark background, spectral accents)
- Preview before send: Every template must render in the React Email preview before deployment
Recommended Stack
| Tool | Purpose | Install |
|---|---|---|
@react-email/components |
Component library for email templates | pnpm add @react-email/components |
react-email |
Dev server for previewing templates | pnpm add -D react-email |
resend |
Email delivery API (recommended) | pnpm add resend |
Alternative Providers
| Provider | When to Use |
|---|---|
| Resend | Recommended — React Email native, simple API |
| SendGrid | Enterprise — high volume, advanced analytics |
| AWS SES | Cost-effective — already on AWS infrastructure |
| SMTP | Self-hosted — full control, no vendor lock-in |
File Structure
apps/web/
├── emails/ # Email templates
│ ├── components/ # Shared email components
│ │ ├── email-header.tsx # Logo + brand header
│ │ ├── email-footer.tsx # Unsubscribe + address
│ │ └── email-button.tsx # CTA button
│ ├── welcome.tsx # Welcome email
│ ├── password-reset.tsx # Password reset
│ ├── daily-report.tsx # Daily agent report
│ └── alert-notification.tsx # System alert
├── lib/email/
│ ├── send.ts # Email sending utility
│ └── config.ts # Provider configuration
Template Patterns
Base Layout Component
import {
Body,
Container,
Head,
Html,
Preview,
Section,
} from '@react-email/components';
interface EmailLayoutProps {
preview: string;
children: React.ReactNode;
}
export function EmailLayout({ preview, children }: EmailLayoutProps) {
return (
<Html>
<Head />
<Preview>{preview}</Preview>
<Body style={body}>
<Container style={container}>
{children}
</Container>
</Body>
</Html>
);
}
// Scientific Luxury email styles (inline for compatibility)
const body = {
backgroundColor: '#050505',
fontFamily: "'Inter', 'SF Pro Display', Helvetica, Arial, sans-serif",
margin: '0',
padding: '0',
};
const container = {
backgroundColor: '#0a0a0a',
border: '1px solid rgba(255, 255, 255, 0.06)',
borderRadius: '2px', // rounded-sm equivalent
margin: '40px auto',
maxWidth: '560px',
padding: '32px',
};
Shared Components
import { Heading, Hr, Link, Text } from '@react-email/components';
// Header with brand name
export function EmailHeader({ title }: { title: string }) {
return (
<>
<Text style={brandLabel}>NODEJS-STARTER-V1</Text>
<Heading style={heading}>{title}</Heading>
<Hr style={divider} />
</>
);
}
// CTA Button — spectral cyan accent
export function EmailButton({ href, children }: { href: string; children: string }) {
return (
<Link href={href} style={button}>
{children}
</Link>
);
}
// Footer with unsubscribe
export function EmailFooter() {
return (
<>
<Hr style={divider} />
<Text style={footer}>
Sent by NodeJS-Starter-V1 — Brisbane, QLD, Australia
</Text>
</>
);
}
const brandLabel = {
color: 'rgba(255, 255, 255, 0.3)',
fontSize: '10px',
fontFamily: "'JetBrains Mono', monospace",
letterSpacing: '0.3em',
textTransform: 'uppercase' as const,
margin: '0 0 8px',
};
const heading = {
color: 'rgba(255, 255, 255, 0.9)',
fontSize: '24px',
fontWeight: '200',
letterSpacing: '-0.02em',
margin: '0 0 24px',
};
const divider = {
borderColor: 'rgba(255, 255, 255, 0.06)',
borderWidth: '0.5px',
margin: '24px 0',
};
const button = {
backgroundColor: '#00F5FF',
borderRadius: '2px',
color: '#050505',
display: 'inline-block',
fontFamily: "'Inter', Helvetica, sans-serif",
fontSize: '14px',
fontWeight: '500',
padding: '12px 24px',
textDecoration: 'none',
};
const footer = {
color: 'rgba(255, 255, 255, 0.3)',
fontSize: '11px',
margin: '0',
};
Example: Daily Report Email
import { Text } from '@react-email/components';
import { EmailLayout } from './components/email-layout';
import { EmailHeader, EmailFooter } from './components/email-header';
interface DailyReportProps {
date: string; // DD/MM/YYYY
total: number;
completed: number;
failed: number;
successRate: string;
}
export function DailyReportEmail({
date, total, completed, failed, successRate,
}: DailyReportProps) {
return (
<EmailLayout preview={`Agent Report — ${date}`}>
<EmailHeader title="Daily Agent Report" />
<Text style={metric}>
<span style={metricLabel}>DATE</span>
<br />
<span style={metricValue}>{date}</span>
</Text>
<Text style={metric}>
<span style={metricLabel}>TOTAL RUNS</span>
<br />
<span style={metricValue}>{total}</span>
</Text>
<Text style={metric}>
<span style={metricLabel}>SUCCESS RATE</span>
<br />
<span style={{ ...metricValue, color: '#00FF88' }}>{successRate}</span>
</Text>
{failed > 0 && (
<Text style={metric}>
<span style={metricLabel}>FAILED</span>
<br />
<span style={{ ...metricValue, color: '#FF4444' }}>{failed}</span>
</Text>
)}
<EmailFooter />
</EmailLayout>
);
}
const metric = { margin: '0 0 16px' };
const metricLabel = {
color: 'rgba(255, 255, 255, 0.3)',
fontSize: '10px',
fontFamily: "'JetBrains Mono', monospace",
letterSpacing: '0.2em',
textTransform: 'uppercase' as const,
};
const metricValue = {
color: 'rgba(255, 255, 255, 0.9)',
fontSize: '20px',
fontFamily: "'JetBrains Mono', monospace",
fontWeight: '500',
};
Sending Emails
Resend Integration
// apps/web/lib/email/send.ts
import { Resend } from 'resend';
const resend = new Resend(process.env.RESEND_API_KEY);
interface SendEmailOptions {
to: string | string[];
subject: string;
react: React.ReactElement;
}
export async function sendEmail({ to, subject, react }: SendEmailOptions) {
const { data, error } = await resend.emails.send({
from: 'NodeJS-Starter-V1 <noreply@yourdomain.com.au>',
to,
subject,
react,
});
if (error) {
throw new Error(`Email send failed: ${error.message}`);
}
return data;
}
Cron Job Integration
Wire into the existing daily report cron:
// apps/web/app/api/cron/daily-report/route.ts
import { sendEmail } from '@/lib/email/send';
import { DailyReportEmail } from '@/emails/daily-report';
// After generating the report...
if (process.env.REPORT_EMAIL_RECIPIENTS) {
const recipients = process.env.REPORT_EMAIL_RECIPIENTS.split(',');
await sendEmail({
to: recipients,
subject: `Agent Report — ${report.date}`,
react: DailyReportEmail({
date: new Date(report.date).toLocaleDateString('en-AU'),
total: report.summary.total,
completed: report.summary.completed,
failed: report.summary.failed,
successRate: report.summary.successRate,
}),
});
}
Scientific Luxury Email Palette
Adapted from the project's design tokens for email client compatibility:
| Token | Value | Usage |
|---|---|---|
| Background | #050505 |
Email body |
| Container | #0a0a0a |
Content area |
| Border | rgba(255, 255, 255, 0.06) |
Dividers, container |
| Text primary | rgba(255, 255, 255, 0.9) |
Headings, values |
| Text secondary | rgba(255, 255, 255, 0.7) |
Body text |
| Text muted | rgba(255, 255, 255, 0.3) |
Labels, footer |
| Cyan accent | #00F5FF |
CTA buttons, links |
| Emerald | #00FF88 |
Success metrics |
| Red | #FF4444 |
Failure metrics |
| Amber | #FFB800 |
Warning states |
Email Client Dark Mode Considerations
Some email clients force light mode. Add fallback:
const container = {
backgroundColor: '#0a0a0a',
// Outlook forces white — use border as visual anchor
border: '1px solid #1a1a1a',
};
Preview and Testing
React Email Dev Server
# Add script to package.json
# "email:dev": "email dev --dir apps/web/emails --port 3001"
pnpm email:dev
# Opens http://localhost:3001 with live preview
Programmatic Rendering (For Tests)
import { render } from '@react-email/render';
import { DailyReportEmail } from '@/emails/daily-report';
const html = await render(
DailyReportEmail({
date: '23/01/2026',
total: 42,
completed: 38,
failed: 4,
successRate: '90.5%',
})
);
// html is a string of rendered HTML — can be used in tests or SMTP
Anti-Patterns
| Anti-Pattern | Why It Fails | Correct Approach |
|---|---|---|
| CSS Grid/Flexbox in emails | Not supported in Outlook, Gmail | Table-based layout via React Email |
External <style> blocks |
Stripped by most clients | Inline styles (React Email default) |
| Light theme email from dark app | Inconsistent brand experience | Dark theme email matching Scientific Luxury |
<img> without alt text |
Accessibility failure | Always include descriptive alt |
| Hardcoded recipient addresses | Breaks across environments | Use REPORT_EMAIL_RECIPIENTS env var |
| Sending without preview | Rendering bugs in production | Always preview in React Email dev server |
Checklist for New Email Templates
Design
- Uses Scientific Luxury palette (dark background, spectral accents)
- Table-based layout (no CSS Grid/Flexbox)
-
rounded-smequivalent (2px border-radius) — no large radii - JetBrains Mono for data values, Inter for editorial text
- Single pixel borders (
rgba(255, 255, 255, 0.06))
Implementation
- Extends
EmailLayoutbase component - Includes
<Preview>text for inbox snippet - Uses
EmailHeaderandEmailFootershared components - All images have
altattributes - Props typed with TypeScript interface
Delivery
-
sendEmail()utility used (not direct Resend/SMTP calls) - Recipient from environment variable (not hardcoded)
- Error handling with structured logging
- Tested in React Email dev server
Australian Locale
- Dates formatted as DD/MM/YYYY
- Currency as AUD ($X,XXX.XX)
- Footer references Australian location
- Spelling: colour, behaviour, analyse, organise
Response Format
[AGENT_ACTIVATED]: Email Template
[PHASE]: {Design | Implementation | Review}
[STATUS]: {in_progress | complete}
{email template analysis or implementation guidance}
[NEXT_ACTION]: {what to do next}
Integration Points
Scientific Luxury
- Email palette derived from
apps/web/lib/design-tokens.ts - Dark background, spectral accents, sharp corners, monospace data values
- No standard Bootstrap/Tailwind email themes
Cron Scheduler
- Daily report email wired into
/api/cron/daily-reportroute - Alert emails triggered by cron health check failures
cron-schedulerpatterns for scheduling digest emails
Structured Logging
- Log
email_sent,email_failedevents with recipient count and template name - Include
correlation_idfor tracing email delivery through the pipeline
Error Taxonomy
- Email delivery failures:
SYS_EXTERNAL_EMAIL_PROVIDER(503) - Invalid recipient:
DATA_VALIDATION_INVALID_EMAIL(422) - Template rendering errors:
SYS_RUNTIME_EMAIL_RENDER(500)
Australian Localisation (en-AU)
- Date Format: DD/MM/YYYY in email body
- Currency: AUD ($) — formatted as
$X,XXX.XX - Timezone: AEST/AEDT — timestamps display Australian time
- Spelling: colour, behaviour, analyse, organise, centre
- Footer: Australian business address (Brisbane, QLD)
Weekly Installs
6
Repository
cleanexpo/nodej…arter-v1GitHub Stars
1
First Seen
Feb 28, 2026
Security Audits
Installed on
opencode6
gemini-cli6
github-copilot6
codex6
kimi-cli6
amp6