Agent Skills: Email Template - Responsive Transactional Emails

>-

UncategorizedID: cleanexpo/nodejs-starter-v1/email-template

Install this agent skill to your local

pnpm dlx add-skill https://github.com/CleanExpo/NodeJS-Starter-V1/tree/HEAD/.skills/custom/email-template

Skill Files

Browse the full folder contents for email-template.

Download Skill

Loading file tree…

.skills/custom/email-template/SKILL.md

Skill Metadata

Name
email-template
Description
">-"

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-handler patterns)
  • Generating PDF reports (use pdf-generator patterns)
  • Handling email as CSV import data (use csv-processor instead)

Core Directives

The Four Rules of Email Templates

  1. Table-based layout: Email clients do not support CSS Grid/Flexbox — use <table> wrappers
  2. Inline styles: Most clients strip <style> blocks — React Email handles this automatically
  3. Dark theme first: Scientific Luxury OLED aesthetic adapted for email (dark background, spectral accents)
  4. 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-sm equivalent (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 EmailLayout base component
  • [ ] Includes <Preview> text for inbox snippet
  • [ ] Uses EmailHeader and EmailFooter shared components
  • [ ] All images have alt attributes
  • [ ] 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-report route
  • Alert emails triggered by cron health check failures
  • cron-scheduler patterns for scheduling digest emails

Structured Logging

  • Log email_sent, email_failed events with recipient count and template name
  • Include correlation_id for 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)