# Email Templates as React, Workflows as TypeScript

Source: https://wraps.dev/blog/email-templates-react-workflows-typescript

Developer Experience•10 min read•Wraps Team

# Email Templates as React,Workflows as TypeScript

Write email templates as React components and automation workflows as TypeScript. Version-controlled, type-safe, and code-reviewable—deployed to your AWS account.

React Email templates

TypeScript workflows

Git-native version control

## The Problem

Most email platforms force you into a GUI editor for templates and a drag-and-drop builder for workflows. This means no version control, no type safety, no code review, and no way to test changes before they go live.

-   Template changes can't be reviewed in a PR
-   No way to roll back a bad deploy besides manual restore
-   Workflow logic lives in a UI you can't grep or test
-   Variables are strings with no type checking
-   Collaboration means "don't edit while I'm editing"

Wraps takes a different approach: **templates are React components, workflows are TypeScript files**. Your email infrastructure lives in your repo, reviewed in PRs, deployed with your code.

## Templates as React Components

Email templates are React components using React Email. You get JSX, props with TypeScript types, shared brand constants, and component composition. The CLI compiles them to HTML and uploads to SES.

wraps/templates/welcome.tsx

```
import { Html, Head, Body, Container, Text, Button, Hr } from '@react-email/components';
import brand from '../brand';

export const subject = 'Welcome to {{companyName}}!';
export const emailType = 'transactional' as const;
export const testData = { name: 'Jane', activationUrl: 'https://yourapp.com/activate' };

type WelcomeProps = {
  name: string;
  activationUrl: string;
};

export default function Welcome({ name, activationUrl }: WelcomeProps) {
  return (
    <Html>
      <Head />
      <Body style={{ backgroundColor: brand.backgroundColor }}>
        <Container style={{ maxWidth: 600, margin: '0 auto' }}>
          <Text style={{ fontSize: 24, color: brand.textColor }}>
            Welcome, {name}!
          </Text>
          <Text style={{ color: brand.textColor }}>
            Thanks for signing up. Click below to activate your account.
          </Text>
          <Hr />
          <Button
            href={activationUrl}
            style={{
              backgroundColor: brand.primaryColor,
              color: '#fff',
              padding: '12px 24px',
              borderRadius: 6,
            }}
          >
            Activate Account
          </Button>
        </Container>
      </Body>
    </Html>
  );
}
```

wraps/brand.ts

```
import { defineBrand } from '@wraps.dev/client';

export default defineBrand({
  primaryColor: '#7C3AED',
  secondaryColor: '#6366f1',
  backgroundColor: '#FAFAFA',
  textColor: '#4B5563',
  fontFamily: 'system-ui, -apple-system, sans-serif',
  buttonStyle: 'rounded',
  buttonRadius: '6px',
  companyName: 'YourApp',
  companyAddress: '123 Main St, City, ST 12345',
  // logoUrl: 'https://yourapp.com/logo.png',
});
```

## How Compilation Works

When you run `wraps email templates push`, the CLI compiles your React components through a pipeline that produces SES-compatible HTML, syncs to the dashboard, and writes a lockfile for conflict detection.

#### Template Compilation Pipeline

esbuild

Proxy

React Email

SES

Dashboard

Lockfile

TypeScript compiled to JavaScript with esbuild

#### Fast Compilation

esbuild compiles TypeScript in milliseconds. No webpack, no Babel.

#### Prop Extraction

Template variables are extracted from props for dashboard editing.

#### React Email Rendering

Components rendered to cross-client HTML with inline styles.

#### Lockfile Tracking

A local lockfile prevents accidental overwrites between code and dashboard edits.

## Workflows as TypeScript

Automation workflows are TypeScript files that define triggers, steps, delays, and conditions. They live in your repo alongside templates and deploy with `wraps email workflows push`.

Welcome Sequence

Cascade

Event Trigger

```
import {
  defineWorkflow, sendEmail, delay,
} from '@wraps.dev/client';

export default defineWorkflow({
  name: 'Welcome Sequence',
  trigger: { type: 'event', eventName: 'contact.subscribed' },

  steps: [
    sendEmail('send-welcome', {
      template: 'welcome',
    }),

    delay('wait-3d', { days: 3 }),

    sendEmail('send-tips', {
      template: 'getting-started-tips',
    }),

    delay('wait-7d', { days: 7 }),

    sendEmail('send-case-study', {
      template: 'case-study',
    }),
  ],
});
```

```
import {
  defineWorkflow, cascade,
} from '@wraps.dev/client';

export default defineWorkflow({
  name: 'Re-engagement',
  trigger: { type: 'event', eventName: 'contact.inactive' },

  steps: [
    // Cascade: try each channel, stop on engagement
    ...cascade('re-engage', {
      channels: [
        { type: 'email', template: 'we-miss-you', wait: { days: 3 } },
        { type: 'email', template: 'special-offer', wait: { days: 5 } },
        { type: 'email', template: 'last-chance' },
      ],
    }),
  ],
});
```

```
import {
  defineWorkflow, sendEmail, condition,
} from '@wraps.dev/client';

export default defineWorkflow({
  name: 'Order Confirmation',
  trigger: { type: 'event', eventName: 'order.completed' },

  steps: [
    sendEmail('send-receipt', {
      template: 'order-receipt',
    }),

    condition('check-vip', {
      field: 'event.total',
      operator: 'greater_than',
      value: 100,
      branches: {
        yes: [
          sendEmail('send-vip-thanks', {
            template: 'vip-thank-you',
          }),
        ],
        no: [],
      },
    }),
  ],
});
```

### Workflow Primitives

Workflows are built from composable primitives. Here are the most common ones — click a row to see an example.

| Primitive | Description |  |
| --- | --- | --- |
| `sendEmail` | Send an email using a named template with variable substitution |  |
| `delay` | Wait a duration before continuing (minutes, hours, days, weeks) |  |
| `condition` | Branch on a field comparison with yes/no paths |  |
| `cascade` | Try channels sequentially, stop when the contact engages |  |

## The Cascade Primitive

Cascade is designed for sequences where you want to stop as soon as the contact engages. Each step waits for a specified duration. If the contact opens or clicks during that window, the cascade stops. Otherwise, it moves to the next step.

wraps/workflows/win-back.ts

```
import { defineWorkflow, cascade } from '@wraps.dev/client';

export default defineWorkflow({
  name: 'Win-Back Campaign',
  trigger: { type: 'event', eventName: 'contact.churned' },

  steps: [
    // Spread cascade — it expands to send + wait + condition nodes
    ...cascade('win-back', {
      channels: [
        {
          type: 'email',
          template: 'we-miss-you',
          wait: { days: 3 },
          // If they open/click within 3 days, stop here
        },
        {
          type: 'email',
          template: 'heres-whats-new',
          wait: { days: 5 },
          // If they engage with this one, stop
        },
        {
          type: 'email',
          template: 'final-offer',
          // Last channel — no wait, cascade ends
        },
      ],
    }),
  ],
});
```

This replaces complex branching logic in traditional workflow builders. One primitive handles the entire re-engagement pattern, and the logic is readable in a code review.

## Code and Dashboard Stay in Sync

Templates pushed from code appear in the dashboard for visual editing. Edits made in the dashboard are tracked separately. The CLI detects when both sides have changed and warns you before overwriting.

#### How Sync Works

-   A local lockfile tracks the SHA256 hash of each pushed template
-   Dashboard edits are tracked separately with a lastEditedFrom field
-   templates push compares hashes — warns on conflict, skips unchanged
-   Use --force to overwrite dashboard edits when pushing from code

## Send with the SDK

Install the SDK to send emails using your templates:

terminal

```
npm install @wraps.dev/email
```

Send using a named SES template with data substitution, or render React components to HTML and send directly.

Templated Send

React Email Send

```
import { WrapsEmail } from '@wraps.dev/email';

const email = new WrapsEmail();

await email.sendTemplate({
  from: 'hello@yourapp.com',
  to: 'user@example.com',
  template: 'welcome',
  templateData: {
    name: 'Jane',
    activationUrl: 'https://yourapp.com/activate?token=abc',
  },
});
```

```
import { WrapsEmail } from '@wraps.dev/email';
import { WelcomeEmail } from './templates/welcome';
import { render } from '@react-email/render';

const email = new WrapsEmail();

const html = await render(
  WelcomeEmail({ name: 'Jane' })
);

await email.send({
  from: 'hello@yourapp.com',
  to: 'user@example.com',
  subject: 'Welcome to YourApp!',
  html,
});
```

## Why This Matters

Moving email templates and workflows into code gives you the same guarantees you expect from application development: version control, type safety, testing, and rollbacks.

|  | Traditional | Wraps |
| --- | --- | --- |
| Templates | GUI editor only | React components + GUI |
| Version control | Manual exports | Git-native |
| Type safety | None | Full TypeScript |
| Code review | Screenshot diffs | PR diffs |
| Workflows | Drag-and-drop | TypeScript + visual builder |
| Testing | Manual sends | Unit testable |
| Rollback | Manual restore | git revert |
| Collaboration | One editor at a time | Branches + PRs |

## Getting Started

Initialize a templates directory, write your first template, and push it to SES in under a minute.

terminal

```
# Initialize templates directory
npx @wraps.dev/cli email templates init

# Edit wraps/templates/welcome.tsx

# Push to SES + dashboard
npx @wraps.dev/cli email templates push

# Push workflows
npx @wraps.dev/cli email workflows push
```

### See It In Action

```
◐ Scaffolding templates directory...✓ Created wraps/templates/  ✓ welcome.tsx  ✓ brand.ts  ✓ wraps.config.ts✓ Templates initialized! ◐ Compiling templates...  ✓ welcome.tsx → welcome (esbuild 12ms)◐ Uploading to SES...✓ 1 template pushed to SES✓ Dashboard synced✓ Lockfile updated
```

## Continue Learning

[

### Templates Quickstart

Step-by-step guide to your first React Email template

](https://wraps.dev/docs/guides/templates)[

### Workflows Quickstart

Build your first automation workflow in TypeScript

](https://wraps.dev/docs/guides/workflows)[

### Inbound Email Guide

Receive and process emails in your AWS account

](https://wraps.dev/blog/inbound-email-guide)[

### CLI Reference

Full CLI documentation and commands

](https://wraps.dev/cli)

## Ready to code your email?

Write templates as React, workflows as TypeScript. Deploy to your AWS account with one command.

npx @wraps.dev/cli email templates init

[Get Started](https://wraps.dev/docs/guides/templates)
