Installs: 0
Used in: 1 repos
Updated: 1d ago
$npx ai-builder add skill nathanielcrowell12-spec/resend

Installs to .claude/skills/resend/

# ⚠️ MANDATORY WORKFLOW - DO NOT SKIP

**When this skill activates, you MUST follow the expert workflow before writing any code:**

1. **Spawn Domain Expert** using the Task tool with this prompt:
   ```
   Read the expert prompt at: C:\Users\natha\Stone-Fence-Brain\VENTURES\PhotoVault\claude\experts\resend-expert.md

   Then research the codebase and write an implementation plan to: docs/claude/plans/email-[task-name]-plan.md

   Task: [describe the user's request]
   ```

2. **Spawn QA Critic** after expert returns, using Task tool:
   ```
   Read the QA critic prompt at: C:\Users\natha\Stone-Fence-Brain\VENTURES\PhotoVault\claude\experts\qa-critic-expert.md

   Review the plan at: docs/claude/plans/email-[task-name]-plan.md
   Write critique to: docs/claude/plans/email-[task-name]-critique.md
   ```

3. **Present BOTH plan and critique to user** - wait for approval before implementing

**DO NOT read files and start coding. DO NOT rationalize that "this is simple." Follow the workflow.**

---

# Resend Email Integration

## Core Principles

### Email HTML is NOT Web HTML

Email clients strip `<style>` tags, ignore CSS classes, and render tables differently. Everything must be inline.

```tsx
// ❌ BAD: CSS classes don't work
<div className="button">Click me</div>

// ✅ GOOD: Inline styles
<a href={url} style={{
  backgroundColor: '#f59e0b',
  color: '#000000',
  padding: '12px 24px',
  borderRadius: '8px',
  textDecoration: 'none',
  display: 'inline-block',
}}>
  Click me
</a>
```

### Mobile First (60%+ opens)

Most emails are read on phones. Design for 320px width first.

```tsx
const container = {
  maxWidth: '600px',
  padding: '20px',
  margin: '0 auto',
}
```

### Every Email Needs a Preview

The preview text appears in the inbox next to the subject.

```tsx
import { Preview } from '@react-email/components'
<Preview>Your gallery "Smith Wedding" is ready with 247 photos</Preview>
```

## Anti-Patterns

**Using CSS classes or external stylesheets**
```tsx
// WRONG: Won't render
<style>{`.button { background: blue; }`}</style>
<a className="button">Click</a>

// RIGHT: Inline everything
<a style={{ backgroundColor: 'blue', padding: '12px 24px' }}>Click</a>
```

**Using flexbox or grid**
```tsx
// WRONG: Not supported in most email clients
<div style={{ display: 'flex' }}>

// RIGHT: Use tables for layout
import { Row, Column } from '@react-email/components'
<Row>
  <Column>Left content</Column>
  <Column>Right content</Column>
</Row>
```

**Forgetting alt text on images**
```tsx
// WRONG: Images often blocked
<Img src={url} />

// RIGHT: Always include meaningful alt
<Img src={url} alt="Preview of your wedding photos" />
```

**Generic subject lines**
```typescript
// WRONG: Low open rate
subject: 'Update from PhotoVault'

// RIGHT: Specific and actionable
subject: 'Your "Smith Wedding" gallery is ready - 247 photos inside'
```

**Not handling send failures**
```typescript
// WRONG: Silent failure
await resend.emails.send({ ... })

// RIGHT: Handle errors
const { data, error } = await resend.emails.send({ ... })
if (error) {
  console.error('Email failed:', error)
}
```

## Email Template Pattern

```tsx
// src/lib/email/templates/gallery-ready.tsx
import {
  Body, Container, Head, Heading, Html,
  Img, Link, Preview, Section, Text,
} from '@react-email/components'

interface GalleryReadyEmailProps {
  clientName: string
  galleryName: string
  photoCount: number
  previewImageUrl: string
  galleryUrl: string
}

export function GalleryReadyEmail({
  clientName, galleryName, photoCount, previewImageUrl, galleryUrl,
}: GalleryReadyEmailProps) {
  return (
    <Html>
      <Head />
      <Preview>Your "{galleryName}" gallery is ready - {photoCount} photos inside</Preview>
      <Body style={main}>
        <Container style={container}>
          <Img src="https://photovault.photo/logo.png" alt="PhotoVault" width={150} />
          <Heading style={heading}>Hi {clientName}!</Heading>
          <Text style={text}>
            Your photos from <strong>{galleryName}</strong> are ready.
            Your photographer has uploaded {photoCount} photos.
          </Text>
          {previewImageUrl && (
            <Img src={previewImageUrl} alt={`Preview from ${galleryName}`} width={560} />
          )}
          <Section style={{ textAlign: 'center', marginTop: '30px' }}>
            <Link href={galleryUrl} style={button}>View Your Photos</Link>
          </Section>
        </Container>
      </Body>
    </Html>
  )
}

const main = {
  backgroundColor: '#0a0a0a',
  fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
}

const container = { maxWidth: '600px', margin: '0 auto', padding: '40px 20px' }
const heading = { color: '#ffffff', fontSize: '28px', fontWeight: 'bold' }
const text = { color: '#a3a3a3', fontSize: '16px', lineHeight: '26px' }
const button = {
  backgroundColor: '#f59e0b',
  color: '#000000',
  padding: '14px 28px',
  borderRadius: '8px',
  textDecoration: 'none',
  fontWeight: 'bold',
  display: 'inline-block',
}
```

## Email Service

```typescript
// src/lib/email/email-service.ts
import { Resend } from 'resend'

const resend = new Resend(process.env.RESEND_API_KEY)

interface SendEmailParams {
  to: string | string[]
  subject: string
  react: React.ReactElement
  replyTo?: string
}

export async function sendEmail({ to, subject, react, replyTo }: SendEmailParams) {
  try {
    const { data, error } = await resend.emails.send({
      from: 'PhotoVault <noreply@photovault.photo>',
      to: Array.isArray(to) ? to : [to],
      subject,
      react,
      replyTo: replyTo || 'support@photovault.photo',
    })

    if (error) {
      console.error('[Email] Send failed:', error)
      return { success: false, error }
    }

    console.log('[Email] Sent successfully:', data?.id)
    return { success: true, id: data?.id }
  } catch (error) {
    console.error('[Email] Unexpected error:', error)
    return { success: false, error }
  }
}
```

## PhotoVault Configuration

### Templates Needed

| Template | Trigger | Recipient |
|----------|---------|-----------|
| `gallery-ready` | Photographer marks ready | Client |
| `payment-success` | Checkout completed | Client |
| `payment-failed` | Invoice failed | Client |
| `invitation` | Photographer invites client | Client |
| `commission-earned` | Client pays | Photographer |

### Branding

| Element | Value |
|---------|-------|
| Primary color | `#f59e0b` (amber) |
| Background | `#0a0a0a` (near black) |
| Text | `#a3a3a3` (gray) |
| Headings | `#ffffff` (white) |

### Environment Variables

```bash
RESEND_API_KEY=re_...
FROM_EMAIL=PhotoVault <noreply@photovault.photo>
```

## Testing Emails

```bash
# Preview locally
npx react-email dev
```

## Deliverability Checklist

1. ✅ Domain verified in Resend (DKIM, SPF)
2. ✅ FROM address uses verified domain
3. ✅ Subject line is specific, not spammy
4. ✅ Alt text on all images
5. ✅ No URL shorteners
6. ✅ Reply-to address is valid

Quick Install

$npx ai-builder add skill nathanielcrowell12-spec/resend

Details

Type
skill
Slug
nathanielcrowell12-spec/resend
Created
5d ago