Skip to content

kylesf/python-react-email

Repository files navigation

Python React Email Template System

A hybrid system that uses React Email for building beautiful email templates and Python for sending them. This system allows you to:

  1. Design emails using React components
  2. Build them into HTML templates with placeholders
  3. Send them using Python with dynamic content

Setup

Prerequisites

  • Python 3.11+
  • Node.js 18+
  • uv (modern Python package installer)
  • pre-commit (optional, for development)

Quick Start

# Install all dependencies and run checks
make all

# Start development server
make dev

Manual Installation

  1. Install all dependencies:
make install
  1. Render email templates:
# Render all templates
make render

# Render a specific template
make render/discover    # Renders only the discover template

Available Commands

# Development
make dev          # Start development server
make preview     # Preview email templates
make export      # Export email templates
make render      # Render all email templates
make render/NAME # Render specific email template

# Dependencies
make install     # Install all dependencies
make update      # Update dependencies and lock files
make sync        # Sync dependencies from lock files
make clean       # Clean all build and cache directories

# Code Quality
make format      # Format all code
make lint        # Lint all code
make check       # Run all checks (format and lint)

# Show all commands
make help

Project Structure

.
├── emails/                  # React email templates
│   ├── components/         # Reusable email components
│   │   └── ImageWithFallback.tsx
│   ├── preview-data/      # Preview data for development
│   │   └── discover.ts
│   ├── template-data/     # Template placeholders for build
│   │   └── discover.ts
│   └── discover.tsx        # Discovery email template
├── rendered/               # Output directory for built templates
├── buildEmails.ts         # Template build script
└── example_usage.py       # Python example for sending emails

Usage

Building Email Templates

  1. Build all email templates:
npm run render
  1. Build a specific template:
npm run render discover    # Builds only the discover template

The built templates will be in the rendered/ directory with placeholders in the format {{variable_name}}.

Template Development

Each email template should:

  1. Export a React component with the email content
  2. Include PreviewProps for development testing
  3. Include template data for build-time placeholder generation
  4. Use components from @react-email/components

Example template structure:

// Interface for the template props
interface EmailProps {
  name: string;
  content: string;
}

// Main template component
export const EmailTemplate = ({ name, content }: EmailProps) => (
  <Html>
    <Body>
      <Text>Hello {name}</Text>
      <Text>{content}</Text>
    </Body>
  </Html>
);

// Preview data for development
EmailTemplate.PreviewProps = {
  name: "John Doe",
  content: "Welcome to our platform!"
};

Template data for build (in template-data/):

export const templateData = {
  name: '{{name}}',
  content: '{{content}}',
};

Sending Emails from Python

The system uses Pydantic for type-safe data handling with the following features:

  • Automatic camelCase conversion for React compatibility
  • Built-in date formatting with ordinal suffixes (e.g., "January 1st, 2025")
  • Consistent serialization across all models
  • Automatic data flattening for template variables

Base configuration:

from datetime import datetime
from pydantic import BaseModel, ConfigDict

class BaseEmailModel(BaseModel):
    """Base class for all email models with consistent serialization."""
    model_config = ConfigDict(
        populate_by_name=True,
        json_schema_serialization_defaults_required=True,
        validate_assignment=True,
        str_strip_whitespace=True,
        extra='forbid'
    )

    @field_validator('*', mode='before')
    @classmethod
    def strip_strings(cls, v: Any) -> Any:
        """Strip whitespace from strings."""
        if isinstance(v, str):
            return v.strip()
        return v

Example model usage:

class ContentItem(BaseEmailModel):
    id: str
    url: str
    title: str
    author: Optional[str] = None
    published_date: Optional[datetime] = None
    summary: str
    extras: Optional[ImageExtras] = None

    def get_content_type(self) -> str:
        """Determine content type based on URL."""
        if 'youtube.com' in self.url:
            return 'YouTube'
        elif 'x.com' in self.url or 'twitter.com' in self.url:
            return 'X'
        return 'Article'

    def model_dump(self, *args, **kwargs) -> Dict[str, Any]:
        """Override model_dump to include computed fields."""
        base_dict = super().model_dump(*args, **kwargs)
        base_dict['content_type'] = self.get_content_type()
        return base_dict

Sending an email:

email_sender = EmailSender()
await email_sender.send_email(
    template_name="discover",
    to_email="user@example.com",
    data=your_data_model,
    subject="Your Weekly Digest"
)

Available Email Templates

Discovery Email (discover.tsx)

A weekly digest email template with:

  • Featured content section (up to 4 items)
  • Additional content list
  • Restaurant of the week
  • Responsive design
  • Image fallback support
  • Content type detection (Article/YouTube/X)
  • Consistent date formatting

Required data structure:

class ImageExtras(BaseEmailModel):
    image_links: Optional[List[str]] = None

class ContentItem(BaseEmailModel):
    id: str
    url: str
    title: str
    author: Optional[str] = None
    published_date: Optional[datetime] = None
    summary: str
    extras: Optional[ImageExtras] = None

class RestaurantSpot(BaseEmailModel):
    name: str
    location: str
    description: str
    image_url: str
    details_url: Optional[str] = None

class DiscoveryEmailData(BaseEmailModel):
    autoprompt_string: str
    featured_content: List[ContentItem]
    additional_content: List[ContentItem]
    restaurant_spot: RestaurantSpot

Environment Variables

Create a .env file in your project root:

RESEND_API_KEY=your_api_key_here

Code Formatting and Linting

The project uses Prettier for frontend (TypeScript/React) and Ruff for backend (Python) code formatting and linting, along with mypy for Python type checking.

Available Commands

# Format all code (frontend and backend)
make format

# Format only backend code
make format-backend

# Format only frontend code
make format-frontend

# Lint all code
make lint

# Lint only backend code
make lint-backend

# Lint only frontend code
make lint-frontend

# Run all checks (format and lint)
make check

# Show all available commands
make help

Frontend Formatting

  • Uses Prettier with import sorting
  • Formats TypeScript, React, JSON, and Markdown files
  • Consistent code style across all frontend files

Backend Formatting and Linting

  • Uses Ruff for both formatting and linting
    • Enforces PEP 8 style guide
    • Sorts imports automatically
    • Fixes common issues
    • Configured via ruff.toml
  • Uses mypy for static type checking
    • Strict type checking enabled
    • Pydantic plugin integration
    • Configured via mypy.ini

Key Features

  1. Type Safety

    • Full type checking with Pydantic models
    • TypeScript interfaces for React components
    • Automatic validation of email data
    • Clear error messages for invalid data
  2. Template System

    • React-based email templates
    • Development preview data
    • Build-time placeholder generation
    • Component reusability
    • Image fallback support
  3. Data Processing

    • Automatic camelCase/snake_case conversion
    • Nested data flattening
    • Computed fields support
    • Content type detection
    • Consistent date formatting
  4. Development Workflow

    • Hot reload for template development
    • Preview data for testing
    • Build system for production
    • Comprehensive logging

Contributing

  1. Create new email templates in the emails/ directory
  2. Add corresponding preview data in emails/preview-data/
  3. Add template data in emails/template-data/
  4. Use existing components or create new ones in emails/components/
  5. Build and test the template
  6. Update this README with new template documentation

License

MIT

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published