How sufi-an/email-marketer works
1. Overall shape
- This is a two-tier application: a FastAPI backend that owns all business logic and persistence, and a Next.js frontend that acts as the operator UI.
- Docker Compose orchestrates the whole stack with PostgreSQL as the shared state store.
2. Backend entrypoint and lifecycle
- backend/app/main.py is the backend composition root: it creates the FastAPI app, configures CORS, wires routers, and starts the APScheduler job on startup.
- The backend also exposes /health for container and service checks.
3. Core domain model
- The system revolves around four persistent concepts in backend/app/models/: User, Campaign, Recipient, and SendLog.
- Campaign is the controlling aggregate: it has a daily_limit and status, and recipients belong to a campaign.
- SendLog is an append-only audit trail for email attempts and outcomes.
4. Authentication boundary
- Authentication is handled in backend/app/routers/auth.py and backend/app/services/auth_service.py.
- The frontend obtains a JWT via POST /auth/login, and all campaign operations require that bearer token.
- This keeps auth concerns isolated from campaign and batch processing logic.
5. Campaign management API
- backend/app/routers/campaigns.py exposes the operational API: create/update/list campaigns, fetch stats, and manage recipients.
- Request/response contracts live in backend/app/schemas/, so the routers stay thin and mostly validate then delegate.
- Campaign stats are derived data, combining recipient state and send progress.
6. Recipient ingestion flow
- Recipients can be added either as JSON or via CSV upload.
- The frontend has dedicated recipient components for manual entry, CSV upload, and table display.
- On the backend, recipient creation feeds the same campaign-scoped persistence model, which then becomes the source pool for batch selection.
7. Batch sending pipeline
- The batch workflow is centered in backend/app/services/batch_service.py.
- A batch run selects up to daily_limit pending recipients for a campaign, sends them, and records results in SendLog.
- The readme’s preview endpoint indicates the service can compute the next batch without side effects, while trigger performs the actual send.
8. Email delivery boundary
- backend/app/services/email_service.py is the outbound integration point for SMTP.
- Delivery is environment-driven: in dry-run mode it logs instead of sending, otherwise it uses SMTP settings from config.
- This makes email delivery a replaceable boundary between internal batch logic and external infrastructure.
9. Scheduled automation
- backend/app/tasks/daily_send.py defines the APScheduler job that runs at a configured UTC time each day.
- That job is the autonomous path that advances campaigns without user interaction, using the same batch service as manual triggers.
- The scheduler is therefore an orchestration layer, not a separate business system.
10. Persistence layer
- backend/app/database.py configures the async SQLAlchemy engine and session factory against PostgreSQL via asyncpg.
- backend/docker/init.sql and the seed path suggest first-run database initialization is handled in-container.
- Tests use a separate database, which keeps the runtime and test persistence boundaries distinct.
11. Frontend structure
- The UI is a Next.js 15 App Router application under frontend/src/app/, with pages for login, campaign listing, campaign detail, and campaign creation.
- Components are split by concern: campaigns, recipients, batch controls, and shared UI primitives.
- frontend/src/components/providers/query-provider.tsx indicates TanStack Query is the client-side data/cache boundary.
12. Frontend-to-backend integration
- All API access is centralized in frontend/src/lib/api.ts; components are not meant to call fetch directly.
- That creates a clean integration layer for auth headers, error handling, and response shaping.
- frontend/src/middleware.ts likely protects routes and redirects unauthenticated users toward login.
13. Operational feedback loop
- The campaign detail UI combines recipient tables, summary stats, and a batch panel so an operator can see state, trigger sends, and observe results in one place.
- This maps directly onto backend APIs for stats, preview, trigger, and recipient listing.
- The architecture is therefore feedback-oriented: UI reads derived state, invokes a batch action, then refreshes from the backend source of truth.
14. Notable constraint
- send_logs is explicitly append-only and serves as the audit/history record for all sends, including failures and retries.
- That means downstream reporting and UI state should be built from log accumulation and recipient status transitions, not destructive log mutation.
Main entry points: app App router · main.py App root · campaigns.py Campaign API · batch_service.py Batch service
Components 21
Backend
FastAPI application, domain services, persistence, and scheduled jobs.
- App root · Creates the FastAPI app, wires routers, CORS, health, and scheduler startup.
backend/app/main.py - DB layer · Async engine and session factory used by backend services and routers.
backend/app/database.py - Config · Environment-driven settings for database, SMTP, auth, and runtime behavior.
backend/app/config.py - Auth API · Login endpoint that issues JWTs for operator access.
backend/app/routers/auth.py - Campaign API · Campaign CRUD, stats, recipient management, and batch control endpoints.
backend/app/routers/campaigns.py - Auth service · Validates credentials and produces access tokens.
backend/app/services/auth_service.py - Batch service · Selects recipients, previews batches, triggers sends, and records outcomes.
backend/app/services/batch_service.py - Email service · Outbound delivery boundary for SMTP or dry-run logging.
backend/app/services/email_service.py - Daily send · APSscheduler task that advances campaigns on a configured UTC schedule.
backend/app/tasks/daily_send.py - Domain models · Persistent entities for users, campaigns, recipients, and send logs.
backend/app/models - Contracts · Request and response shapes used by the API layer.
backend/app/schemas - Seed data · Initial data loading path used for local or first-run setup.
backend/app/seed.py
Frontend
Next.js operator UI and client integration layer.
- App router · Pages for login, campaign list, campaign detail, and campaign creation.
frontend/src/app - Client API · Centralized backend API access for auth headers, errors, and response shaping.
frontend/src/lib/api.ts - Query provider · TanStack Query boundary for frontend data fetching and caching.
frontend/src/components/providers/query-provider.tsx - Campaign UI · Campaign cards, stats, and detail views for operator feedback.
frontend/src/components/campaigns - Recipient UI · Manual entry, CSV upload, and recipient table components.
frontend/src/components/recipients - Batch panel · Operator control for previewing and triggering sends from campaign detail.
frontend/src/components/batch/batch-panel.tsx - Route guard · Protects frontend routes and redirects unauthenticated users.
frontend/src/middleware.ts
Infra
Database and composition/orchestration around the app stack.
- Compose · Starts the backend, frontend, and shared database services.
docker-compose.yml - PostgreSQL · Shared persistent database for users, campaigns, recipients, and send logs.
backend/docker/init.sql
Connections 24
- Compose → App root: starts · Runs the backend service as part of the composed stack.
- Compose → App router: starts · Runs the Next.js frontend service as part of the composed stack.
- Compose → PostgreSQL: provisions · Connects both runtimes to the shared PostgreSQL instance.
- App root → Auth API: mounts · Registers the auth router on the FastAPI app.
- App root → Campaign API: mounts · Registers campaign endpoints on the FastAPI app.
- App root → Daily send: starts · Starts the scheduled daily send job on app startup.
- App root → DB layer: uses · Shares the async database layer across request handlers.
- Auth API → Auth service: delegates · Hands login requests to the auth service.
- Campaign API → Contracts: validates · Uses schemas for request and response contracts.
- Campaign API → Batch service: delegates · Hands preview and trigger operations to batch orchestration.
- Batch service → Domain models: reads/writes · Loads campaigns and recipients, then persists send results.
- Batch service → Email service: sends via · Uses the email boundary for delivery or dry-run logging.
- Daily send → Batch service: invokes · Reuses the batch service for autonomous daily sending.
- DB layer → PostgreSQL: connects to · Async SQLAlchemy connects to PostgreSQL via asyncpg.
- Domain models → PostgreSQL: persists in · ORM entities are stored in the shared database.
- Seed data → PostgreSQL: initializes · Seeds or initializes database state on first run.
- App router → Client API: calls · Pages use the centralized API client rather than raw fetch calls.
- App router → Query provider: wraps · Uses the query provider for client-side data state.
- App router → Campaign UI: composes · Builds campaign list and detail experiences from campaign components.
- App router → Recipient UI: composes · Builds recipient management flows from recipient components.
- App router → Batch panel: composes · Places batch preview and trigger controls in the campaign detail view.
- Route guard → App router: guards · Protects app routes before page rendering.
- Client API → Auth API: authenticates with · Logs in to obtain JWTs for subsequent API requests.
- Client API → Campaign API: requests · Fetches campaign, recipient, and batch data from the backend API.