A hybrid system that uses React Email for building beautiful email templates and Python for sending them. This system allows you to:
- Design emails using React components
- Build them into HTML templates with placeholders
- Send them using Python with dynamic content
- Python 3.11+
- Node.js 18+
- uv (modern Python package installer)
- pre-commit (optional, for development)
# Install all dependencies and run checks
make all
# Start development server
make dev- Install all dependencies:
make install- Render email templates:
# Render all templates
make render
# Render a specific template
make render/discover # Renders only the discover template# 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.
├── 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
- Build all email templates:
npm run render- Build a specific template:
npm run render discover # Builds only the discover templateThe built templates will be in the rendered/ directory with placeholders in the format {{variable_name}}.
Each email template should:
- Export a React component with the email content
- Include
PreviewPropsfor development testing - Include template data for build-time placeholder generation
- 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}}',
};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 vExample 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_dictSending 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"
)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: RestaurantSpotCreate a .env file in your project root:
RESEND_API_KEY=your_api_key_hereThe project uses Prettier for frontend (TypeScript/React) and Ruff for backend (Python) code formatting and linting, along with mypy for Python type checking.
# 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- Uses Prettier with import sorting
- Formats TypeScript, React, JSON, and Markdown files
- Consistent code style across all frontend files
- 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
-
Type Safety
- Full type checking with Pydantic models
- TypeScript interfaces for React components
- Automatic validation of email data
- Clear error messages for invalid data
-
Template System
- React-based email templates
- Development preview data
- Build-time placeholder generation
- Component reusability
- Image fallback support
-
Data Processing
- Automatic camelCase/snake_case conversion
- Nested data flattening
- Computed fields support
- Content type detection
- Consistent date formatting
-
Development Workflow
- Hot reload for template development
- Preview data for testing
- Build system for production
- Comprehensive logging
- Create new email templates in the
emails/directory - Add corresponding preview data in
emails/preview-data/ - Add template data in
emails/template-data/ - Use existing components or create new ones in
emails/components/ - Build and test the template
- Update this README with new template documentation
MIT