# sufi-an/email-marketer architecture

> Architecture diagram of the GitHub repository sufi-an/email-marketer, generated by GitDiagram from its file tree, README and a sample of source files (last updated 2026-06-28). It is an AI-made overview: check details against the source.

- Interactive diagram: https://gitdiagram.com/sufi-an/email-marketer
- Repository: https://github.com/sufi-an/email-marketer

## Overview

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: [`frontend/src/app`](https://github.com/sufi-an/email-marketer/tree/main/frontend/src/app) (App router), [`backend/app/main.py`](https://github.com/sufi-an/email-marketer/blob/main/backend/app/main.py) (App root), [`backend/app/routers/campaigns.py`](https://github.com/sufi-an/email-marketer/blob/main/backend/app/routers/campaigns.py) (Campaign API), [`backend/app/services/batch_service.py`](https://github.com/sufi-an/email-marketer/blob/main/backend/app/services/batch_service.py) (Batch service)

## Diagram (Mermaid)

```mermaid
flowchart TD

subgraph group_group_backend["Backend"]
  node_node_backend_main["App root<br/>api bootstrap<br/>[main.py]"]
  node_node_backend_db["DB layer<br/>sqlalchemy async<br/>[database.py]"]
  node_node_backend_config["Config<br/>env settings<br/>[config.py]"]
  node_node_auth_router["Auth API<br/>router<br/>[auth.py]"]
  node_node_campaign_router["Campaign API<br/>router<br/>[campaigns.py]"]
  node_node_auth_service["Auth service<br/>jwt auth<br/>[auth_service.py]"]
  node_node_batch_service["Batch service<br/>send orchestration<br/>[batch_service.py]"]
  node_node_email_service["Email service<br/>smtp adapter<br/>[email_service.py]"]
  node_node_daily_task["Daily send<br/>scheduler job<br/>[daily_send.py]"]
  node_node_models{{"Domain models<br/>orm models"}}
  node_node_schemas["Contracts<br/>pydantic schemas"]
  node_node_seed["Seed data<br/>bootstrap data<br/>[seed.py]"]
end

subgraph group_group_frontend["Frontend"]
  node_node_frontend_app["App router<br/>next.js app"]
  node_node_frontend_api["Client API<br/>fetch wrapper<br/>[api.ts]"]
  node_node_query_provider["Query provider<br/>client cache<br/>[query-provider.tsx]"]
  node_node_campaign_ui["Campaign UI<br/>ui feature"]
  node_node_recipient_ui["Recipient UI<br/>ui feature"]
  node_node_batch_panel["Batch panel<br/>ui control<br/>[batch-panel.tsx]"]
  node_node_middleware["Route guard<br/>next middleware<br/>[middleware.ts]"]
end

subgraph group_group_infra["Infra"]
  node_node_compose["Compose<br/>orchestrator<br/>[docker-compose.yml]"]
  node_node_db[("PostgreSQL<br/>state store<br/>[init.sql]")]
end

node_node_compose -->|"starts"| node_node_backend_main
node_node_compose -->|"starts"| node_node_frontend_app
node_node_compose -->|"provisions"| node_node_db
node_node_backend_main -->|"mounts"| node_node_auth_router
node_node_backend_main -->|"mounts"| node_node_campaign_router
node_node_backend_main -->|"starts"| node_node_daily_task
node_node_backend_main -->|"uses"| node_node_backend_db
node_node_auth_router -->|"delegates"| node_node_auth_service
node_node_campaign_router -->|"validates"| node_node_schemas
node_node_campaign_router -->|"delegates"| node_node_batch_service
node_node_batch_service -->|"reads/writes"| node_node_models
node_node_batch_service -->|"sends via"| node_node_email_service
node_node_daily_task -->|"invokes"| node_node_batch_service
node_node_backend_db -->|"connects to"| node_node_db
node_node_models -->|"persists in"| node_node_db
node_node_seed -->|"initializes"| node_node_db
node_node_frontend_app -->|"calls"| node_node_frontend_api
node_node_frontend_app -->|"wraps"| node_node_query_provider
node_node_frontend_app -->|"composes"| node_node_campaign_ui
node_node_frontend_app -->|"composes"| node_node_recipient_ui
node_node_frontend_app -->|"composes"| node_node_batch_panel
node_node_middleware -->|"guards"| node_node_frontend_app
node_node_frontend_api -->|"authenticates with"| node_node_auth_router
node_node_frontend_api -->|"requests"| node_node_campaign_router

click node_node_compose "https://github.com/sufi-an/email-marketer/blob/main/docker-compose.yml"
click node_node_db "https://github.com/sufi-an/email-marketer/blob/main/backend/docker/init.sql"
click node_node_backend_main "https://github.com/sufi-an/email-marketer/blob/main/backend/app/main.py"
click node_node_backend_db "https://github.com/sufi-an/email-marketer/blob/main/backend/app/database.py"
click node_node_backend_config "https://github.com/sufi-an/email-marketer/blob/main/backend/app/config.py"
click node_node_auth_router "https://github.com/sufi-an/email-marketer/blob/main/backend/app/routers/auth.py"
click node_node_campaign_router "https://github.com/sufi-an/email-marketer/blob/main/backend/app/routers/campaigns.py"
click node_node_auth_service "https://github.com/sufi-an/email-marketer/blob/main/backend/app/services/auth_service.py"
click node_node_batch_service "https://github.com/sufi-an/email-marketer/blob/main/backend/app/services/batch_service.py"
click node_node_email_service "https://github.com/sufi-an/email-marketer/blob/main/backend/app/services/email_service.py"
click node_node_daily_task "https://github.com/sufi-an/email-marketer/blob/main/backend/app/tasks/daily_send.py"
click node_node_models "https://github.com/sufi-an/email-marketer/tree/main/backend/app/models"
click node_node_schemas "https://github.com/sufi-an/email-marketer/tree/main/backend/app/schemas"
click node_node_seed "https://github.com/sufi-an/email-marketer/blob/main/backend/app/seed.py"
click node_node_frontend_app "https://github.com/sufi-an/email-marketer/tree/main/frontend/src/app"
click node_node_frontend_api "https://github.com/sufi-an/email-marketer/blob/main/frontend/src/lib/api.ts"
click node_node_query_provider "https://github.com/sufi-an/email-marketer/blob/main/frontend/src/components/providers/query-provider.tsx"
click node_node_campaign_ui "https://github.com/sufi-an/email-marketer/tree/main/frontend/src/components/campaigns"
click node_node_recipient_ui "https://github.com/sufi-an/email-marketer/tree/main/frontend/src/components/recipients"
click node_node_batch_panel "https://github.com/sufi-an/email-marketer/blob/main/frontend/src/components/batch/batch-panel.tsx"
click node_node_middleware "https://github.com/sufi-an/email-marketer/blob/main/frontend/src/middleware.ts"

classDef toneNeutral fill:#f8fafc,stroke:#334155,stroke-width:1.5px,color:#0f172a
classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
class node_node_backend_main,node_node_backend_db,node_node_backend_config,node_node_auth_router,node_node_campaign_router,node_node_auth_service,node_node_batch_service,node_node_email_service,node_node_daily_task,node_node_models,node_node_schemas,node_node_seed toneBlue
class node_node_frontend_app,node_node_frontend_api,node_node_query_provider,node_node_campaign_ui,node_node_recipient_ui,node_node_batch_panel,node_node_middleware toneAmber
class node_node_compose,node_node_db toneMint
```

## Components

### Backend

FastAPI application, domain services, persistence, and scheduled jobs.

- [App root](https://github.com/sufi-an/email-marketer/blob/main/backend/app/main.py): api bootstrap · Creates the FastAPI app, wires routers, CORS, health, and scheduler startup. · `backend/app/main.py`
- [DB layer](https://github.com/sufi-an/email-marketer/blob/main/backend/app/database.py): sqlalchemy async · Async engine and session factory used by backend services and routers. · `backend/app/database.py`
- [Config](https://github.com/sufi-an/email-marketer/blob/main/backend/app/config.py): env settings · Environment-driven settings for database, SMTP, auth, and runtime behavior. · `backend/app/config.py`
- [Auth API](https://github.com/sufi-an/email-marketer/blob/main/backend/app/routers/auth.py): router · Login endpoint that issues JWTs for operator access. · `backend/app/routers/auth.py`
- [Campaign API](https://github.com/sufi-an/email-marketer/blob/main/backend/app/routers/campaigns.py): router · Campaign CRUD, stats, recipient management, and batch control endpoints. · `backend/app/routers/campaigns.py`
- [Auth service](https://github.com/sufi-an/email-marketer/blob/main/backend/app/services/auth_service.py): jwt auth · Validates credentials and produces access tokens. · `backend/app/services/auth_service.py`
- [Batch service](https://github.com/sufi-an/email-marketer/blob/main/backend/app/services/batch_service.py): send orchestration · Selects recipients, previews batches, triggers sends, and records outcomes. · `backend/app/services/batch_service.py`
- [Email service](https://github.com/sufi-an/email-marketer/blob/main/backend/app/services/email_service.py): smtp adapter · Outbound delivery boundary for SMTP or dry-run logging. · `backend/app/services/email_service.py`
- [Daily send](https://github.com/sufi-an/email-marketer/blob/main/backend/app/tasks/daily_send.py): scheduler job · APSscheduler task that advances campaigns on a configured UTC schedule. · `backend/app/tasks/daily_send.py`
- [Domain models](https://github.com/sufi-an/email-marketer/tree/main/backend/app/models): orm models · Persistent entities for users, campaigns, recipients, and send logs. · `backend/app/models`
- [Contracts](https://github.com/sufi-an/email-marketer/tree/main/backend/app/schemas): pydantic schemas · Request and response shapes used by the API layer. · `backend/app/schemas`
- [Seed data](https://github.com/sufi-an/email-marketer/blob/main/backend/app/seed.py): bootstrap 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](https://github.com/sufi-an/email-marketer/tree/main/frontend/src/app): next.js app · Pages for login, campaign list, campaign detail, and campaign creation. · `frontend/src/app`
- [Client API](https://github.com/sufi-an/email-marketer/blob/main/frontend/src/lib/api.ts): fetch wrapper · Centralized backend API access for auth headers, errors, and response shaping. · `frontend/src/lib/api.ts`
- [Query provider](https://github.com/sufi-an/email-marketer/blob/main/frontend/src/components/providers/query-provider.tsx): client cache · TanStack Query boundary for frontend data fetching and caching. · `frontend/src/components/providers/query-provider.tsx`
- [Campaign UI](https://github.com/sufi-an/email-marketer/tree/main/frontend/src/components/campaigns): ui feature · Campaign cards, stats, and detail views for operator feedback. · `frontend/src/components/campaigns`
- [Recipient UI](https://github.com/sufi-an/email-marketer/tree/main/frontend/src/components/recipients): ui feature · Manual entry, CSV upload, and recipient table components. · `frontend/src/components/recipients`
- [Batch panel](https://github.com/sufi-an/email-marketer/blob/main/frontend/src/components/batch/batch-panel.tsx): ui control · Operator control for previewing and triggering sends from campaign detail. · `frontend/src/components/batch/batch-panel.tsx`
- [Route guard](https://github.com/sufi-an/email-marketer/blob/main/frontend/src/middleware.ts): next middleware · Protects frontend routes and redirects unauthenticated users. · `frontend/src/middleware.ts`

### Infra

Database and composition/orchestration around the app stack.

- [Compose](https://github.com/sufi-an/email-marketer/blob/main/docker-compose.yml): orchestrator · Starts the backend, frontend, and shared database services. · `docker-compose.yml`
- [PostgreSQL](https://github.com/sufi-an/email-marketer/blob/main/backend/docker/init.sql): state store · Shared persistent database for users, campaigns, recipients, and send logs. · `backend/docker/init.sql`

## Connections

- 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.
