System Overview
Scribe is a production-ready cold email generation platform built with a backend-first architecture. The system uses FastAPI for the REST API, Celery for asynchronous task processing, and a sophisticated 4-step AI pipeline to generate personalized academic outreach emails.High-Level Architecture
Core Components
FastAPI Backend
REST API handling authentication, request validation, and database operations
Celery Workers
Asynchronous task processing for long-running email generation (10-25s)
PostgreSQL Database
Stores users, emails, queue items, and templates with JSONB metadata
Redis Cache
Task queue broker and result backend with 1-hour TTL
Technology Stack
Backend Framework
| Technology | Version | Purpose |
|---|---|---|
| FastAPI | 0.109+ | High-performance ASGI web framework with automatic validation |
| Python | 3.13 | Modern Python with native async/await support |
| Uvicorn | 0.27+ | Lightning-fast ASGI server implementation |
| Pydantic | 2.5+ | Data validation and settings management |
- Native
async/awaitfor concurrent operations - Automatic Pydantic validation and serialization
- Auto-generated OpenAPI documentation at
/docs - Best-in-class performance (comparable to Node.js/Go)
- Type hints throughout for IDE support
Database Layer
| Technology | Version | Purpose |
|---|---|---|
| PostgreSQL | Latest | Primary database via Supabase managed hosting |
| SQLAlchemy | 2.0+ | Modern ORM with async support and type hints |
| Alembic | 1.13+ | Database migration management |
| Supabase | 2.3+ | Managed PostgreSQL with built-in auth |
- Connection Mode: Transaction Pooler (port 6543)
- Pooling Strategy:
NullPool(no client-side pooling) - SSL Mode: Required (
sslmode=require) - Connection String: Auto-constructed from environment variables
Why NullPool? Supabase’s transaction pooler (Supavisor) handles connection pooling server-side. NullPool creates fresh connections per request, preventing stale connection issues common with client-side pooling.
Task Queue System
| Technology | Version | Purpose |
|---|---|---|
| Celery | 5.3+ | Distributed task queue for async processing |
| Redis | 5.0+ | Message broker and result backend |
| Flower | 2.0+ | Real-time monitoring UI for Celery |
| Kombu | 5.3+ | Messaging library for Celery |
| Feature | Celery + Redis | BackgroundTasks |
|---|---|---|
| Persistent State | ✅ Redis storage | ❌ In-memory only |
| Status Polling | ✅ AsyncResult API | ❌ No polling support |
| Horizontal Scaling | ✅ Multiple workers | ❌ Tied to API process |
| Retry Logic | ✅ Built-in with exponential backoff | ❌ Manual implementation |
| Monitoring | ✅ Flower UI | ❌ No visibility |
| Task Timeouts | ✅ Configurable | ❌ Limited control |
AI & Machine Learning
| Technology | Version | Purpose |
|---|---|---|
| Anthropic Claude | Latest | LLM for email generation (Haiku 4.5, Sonnet 4.5) |
| Pydantic AI | 1.18+ | Structured LLM outputs with type safety |
| Exa Search | 2.0+ | AI-powered web search for recipient research |
| ArXiv API | 2.0+ | Academic paper discovery and citation |
- Superior instruction-following for structured output
- Fast models (Haiku) for extraction tasks
- Powerful models (Sonnet) for creative writing
- Excellent at chain-of-thought reasoning
- Native JSON mode for Pydantic integration
- Modern dual-query strategy (background + publications)
- Better academic content retrieval than Google CSE
- Automatic citation extraction
- AI-powered result ranking
Web Scraping
| Technology | Version | Purpose |
|---|---|---|
| Playwright | 1.56+ | Headless browser for JavaScript-heavy sites |
| BeautifulSoup4 | 4.12+ | HTML parsing and content extraction |
| httpx | 0.27+ | Modern async HTTP client with HTTP/2 |
- Full JavaScript execution (unlike requests/httpx)
- Handles dynamic content loading
- Supports modern web standards
- Headless mode for server deployment
- ~300MB Chromium binary (cached after first install)
Observability
| Technology | Version | Purpose |
|---|---|---|
| Logfire | 4.14+ | Distributed tracing and LLM monitoring |
| Pydantic Integration | Native | Automatic instrumentation |
- All FastAPI requests (path, status, duration)
- Database queries (SQLAlchemy)
- Celery tasks (enqueue, start, success, failure)
- LLM calls (prompts, tokens, cost, latency)
- Pipeline steps (distributed tracing)
Backend-First Authentication
Scribe follows a strict backend-first design where the frontend only uses Supabase for authentication, and the backend handles all database operations.Authentication Flow
Security Model
main.py
- Security: Centralized authorization enforcement
- Flexibility: Schema changes don’t break frontend
- Business Logic: Complex validation in one place
- Auditability: All database operations logged
The 4-Step Pipeline
The email generation pipeline is a stateless, in-memory process that enriches a sharedPipelineData object through four sequential steps.
Pipeline Architecture
Step 1: Template Parser
Input: Template text, recipient infoProcess: Claude analyzes template and extracts search terms
Output:
search_terms (list), template_type (RESEARCH/BOOK/GENERAL)
Example:
Step 2: Web Scraper
Input: Search terms, template typeProcess: Dual-query Exa search (background + publications), content summarization
Output:
scraped_content (with citations), scraped_urls
Dual-Query Strategy:
- Query 1: Background (affiliations, bio, current work)
- Query 2: Publications (papers/books based on template_type)
- Combine: Merge results with proper citations
Step 3: ArXiv Helper
Input: Recipient nameProcess: Search ArXiv API for papers by author
Output:
arxiv_papers (title, abstract, year, url)Condition: Only runs if
template_type == RESEARCH
Skipped for: BOOK and GENERAL templates
Step 4: Email Composer
Input: All gathered data (template, scraped content, papers)Process: Claude Sonnet generates personalized email
Output:
final_email, email_id (UUID)Database Operations:
- Insert email into
emailstable - Increment
user.generation_count - Store metadata (papers, sources, timings) in JSONB field
Pipeline Data Flow
pipeline/models/core.py
Stateless Design:
PipelineData lives entirely in memory during execution. Only the final email is persisted to the database. This design simplifies debugging and reduces database load (1 write instead of 4+).Database Schema
Users Table
Emails Table
Queue Items Table
models/queue_item.py
Deployment Architecture
Self-Hosted on Raspberry Pi
The production backend runs on a Raspberry Pi 3B+ with traffic routed through a Cloudflare Tunnel, providing a persistent, always-on server without cloud hosting costs.Hardware Specs
Raspberry Pi 3B+
- CPU: Quad-core Cortex-A53 @ 1.4GHz
- RAM: 1GB LPDDR2
- Network: Gigabit Ethernet + Wi-Fi
- Storage: Micro-SD
Public Endpoint
https://scribeapi.manitmishra.com
- Cloudflare Tunnel (outbound-only)
- Automatic HTTPS/SSL
- DDoS protection
- No port forwarding needed
Cloudflare Tunnel Setup
Cloudflare Tunnel creates a secure connection from the Raspberry Pi to Cloudflare’s edge network:~/.cloudflared/config.yml):
Production Services
FastAPI Server (/etc/systemd/system/scribe-api.service):
/etc/systemd/system/scribe-worker.service):
Resource Considerations
Memory Breakdown (1GB total):- OS + System: ~200MB
- Python + FastAPI: ~80MB
- Redis: ~30MB
- Celery Worker: ~50MB
- Playwright (active): ~150MB
- Pipeline overhead: ~50MB
- Remaining: ~340-440MB headroom
Scaling Strategy
| Hardware | RAM | Concurrency | Throughput |
|---|---|---|---|
| Pi 3B+ (Current) | 1GB | 1 task | ~3-4 emails/min |
| Pi 4 (2GB) | 2GB | 1-2 tasks | ~6-8 emails/min |
| Pi 4 (4GB) | 4GB | 2-3 tasks | ~12-15 emails/min |
| Pi 5 (8GB) | 8GB | 4-6 tasks | ~24-30 emails/min |
- Add more Raspberry Pis as Celery workers
- Shared Redis instance
- Load-balanced FastAPI instances behind Cloudflare
Observability & Monitoring
Logfire Integration
Logfire provides distributed tracing across the entire request lifecycle:- Step-by-step timings
- LLM token usage and cost
- Database query performance
- API endpoint latency
- Worker queue depth
- Error rates and stack traces
Flower Monitoring UI
Access real-time Celery monitoring athttp://localhost:5555:
- Active tasks and worker status
- Task history and success/failure rates
- Worker resource usage (CPU, memory)
- Task routing and queue depth
- Retry and failure logs
Directory Structure
Key Architectural Decisions
1. Stateless Pipeline Design
Philosophy: Pipeline state lives inPipelineData object in memory. No intermediate database writes—only the final email is persisted.
Benefits:
- Simplified debugging (full trace in Logfire)
- Reduced database load (1 write instead of 4+)
- Idempotent retries (no partial state corruption)
- Better performance (all processing in RAM)
- Cannot resume mid-pipeline (must restart from Step 1)
- Acceptable because tasks only take 10-25s
2. Backend-First (No Direct DB Access)
Why: Security (enforce authorization), flexibility (schema changes don’t break frontend), centralized business logic Trade-off: Extra API latency vs direct Supabase client queries (acceptable for ~50ms overhead)3. Transaction Pooler with NullPool
Why: Supabase’s Supavisor handles connection pooling server-side. Client-side pooling causes stale connection issues. How: NullPool creates fresh connections per request, immediately discarded after use. Benefit: Optimal for single-server deployments (Raspberry Pi). Eliminates connection management complexity.4. Memory-Constrained Design
Context: Raspberry Pi 3B+ has only 1GB RAM Solution: Sequential processing (concurrency=1), single worker, aggressive memory management
Measurement: Each task uses ~400MB with Playwright browser
Future: With 4GB+ RAM → increase concurrency to 2-4
5. Hot-Swappable LLM Models
Implementation: Environment variables control model selection Current Defaults:Common Troubleshooting
| Problem | Solution |
|---|---|
| Worker not picking up tasks | Check Redis: redis-cli ping, restart worker |
| Task stuck in PENDING | Verify worker running: celery -A celery_config.celery_app inspect active |
| Out of memory (OOM) | Reduce worker_concurrency to 1, restart worker |
| Database connection errors | Check .env, verify Supabase credentials and port 6543 |
| Playwright fails | Reinstall: playwright install chromium |
| Stale database connections | Using NullPool should prevent this; check connection string |
Further Reading
Quickstart Guide
Get Scribe running locally in 5 minutes
API Reference
Complete REST API documentation
Pipeline Deep Dive
Detailed implementation of the 4-step pipeline
Development Guide
Testing workflows and debugging techniques
