A fun backend lab where messaging, architecture, and async ideas get tested in public.
This repository is a backend playground for experimenting with messaging systems, architecture patterns, and async workflows.
It is not just a single "send message" API. It is a space to learn, break things safely, and evolve ideas 😄
Think of it as an engineering sandbox where I test concepts in real code.
One of the main goals here is learning how software is structured in enterprise-style systems (layered architecture, clear boundaries, async workflows, and integration-heavy design).
- hexagonal/clean-ish architecture with clear ports and adapters
- pluggable messenger providers (Telegram, WhatsApp)
- outbox pattern + background workers + optional broker mode
- bulk import pipelines (CSV/XLSX) with staging and processing steps
- dependency injection, unit-of-work boundaries, and domain events
At a high level, this backend lets you:
- authenticate users and manage sessions
- connect messenger sessions (OTP/password for Telegram, QR for WhatsApp)
- upload files to S3/MinIO
- create message requests and generate messages from tabular files
- dispatch those messages asynchronously through workers
- switch event dispatch from direct DB worker handling to broker-based handling (RabbitMQ implemented now)
Learning-first note: this project intentionally favors experimentation over polish in some areas, so multiple approaches may coexist while ideas are being compared.
- You can plug in messenger providers behind the same core use cases.
- You can switch async dispatch style (
directvsbroker) without rewriting domain logic. - You can run workers as loops or one-shot jobs (
--once) for CronJob-style scheduling. - You can explore enterprise-style patterns in a project that is still playful and fast to change.
The code is organized by bounded context and layer:
app/: FastAPI entrypoints, routers, DI wiring, runtime compositionsrc/base/: shared abstractions (ports, UoW, outbox, worker plumbing)src/users/: auth/user models, repos, JWT/password adapterssrc/files/: file domain + S3/MinIO integrationsrc/messaging/: messenger domain, providers, use cases, workerssrc/importing/: generic import framework (staging/process/config/handlers)migrations/: Alembic migrations
Client -> FastAPI routes (app/) -> Use cases (src/*/application)
-> Unit of Work (SQLAlchemy repos)
-> Outbox event persisted in DB
-> Worker dispatches event
-> direct mode: execute handler now
-> broker mode: publish to event bus, consumer executes handler
API Request -> DB write -> Outbox row -> Worker tick -> Handler/Bus -> Message send
-
API layer (
app/main.py,app/v1/**)- HTTP contracts, request validation, auth dependency hooks
- very thin endpoints that call application use cases
-
Dependency Injection (
app/container.py)- central composition root using
dependency-injector - selects implementations for DB, cache, messengers, event bus, imports
- central composition root using
-
Domain/Application core (
src/**/application,src/**/domain)- entities and use cases are isolated from framework details
- ports define what infrastructure should provide
-
Infrastructure adapters (
src/**/adapters)- SQLAlchemy repos, Redis cache/staging, S3 file service
- Telegram via Telethon, WhatsApp via Evolution API HTTP adapter
-
Async orchestration (
app/workers.py,app/consumer.py)- polling workers for outbox and CSV generation
- broker consumer abstraction supports different buses; RabbitMQ is the current implementation
These are the "experiments" this repo currently contains:
-
Pluggable messengers with capability discovery
- messenger descriptors infer features/auth/contact methods from interfaces
- supports multiple auth styles and contact identifiers per provider
-
Dual outbox dispatch strategy
outbox_dispatch_strategy=direct: DB worker executes handlers directlyoutbox_dispatch_strategy=broker: worker publishes to event bus, consumer handles
-
Event bus abstraction (pluggable consumers/brokers)
- architecture is designed for multiple bus implementations (e.g., RabbitMQ, Kafka, others)
- current concrete implementation is RabbitMQ
-
Outbox reliability mechanisms
- retry attempts with exponential backoff
- dead-letter behavior after max attempts
- dedup/aggregate metadata kept on outbox rows
-
Two message-generation paths
- generic import pipeline (stage -> process) with typed configs
-
Import framework as reusable engine
- import registry maps
import_typeto config + handler - tabular resolver supports multiple readers (
.csv,.xlsx) - unknown column policies, row-level error capture, staged row processing
- import registry maps
-
UoW and repository boundaries
- explicit transactional boundaries in use cases and workers
- clear separation of write-side repositories and read-side queries
Base:
GET /health/root
V1:
- Users/Auth:
POST /v1/users/auth/loginGET /v1/users/auth/current_user
- Files:
POST /v1/files/uploadGET /v1/files/{file_id}
- Messaging:
POST /v1/messaging/messagePOST /v1/messaging/message-requests/csvPOST /v1/messaging/message-requests/importGET /v1/messaging/message-requests/{message_request_id}
- Session/Messenger discovery + auth:
GET /v1/messaging/sessions/messengersPOST /v1/messaging/sessions/otpPOST /v1/messaging/sessions/qrPOST /v1/messaging/sessions/verify/opt(spelled this way in current route)POST /v1/messaging/sessions/verify/password
Interactive docs are available at /docs when the app is running.
Use this profile for local experimentation. It boots infrastructure services and keeps the backend workflow flexible for local uvicorn/worker runs.
- Copy env template:
cp sample.env .env- Start dev infra:
docker compose -f docker-compose-dev.yml up -d- Run app locally:
uv sync
uv run alembic upgrade head
uv run uvicorn app.main:app --reloadDevelopment setup intent:
- run infrastructure in Docker
- run API/workers locally
- keep feedback loops fast during development
Use this profile to run a fuller stack closer to deployment shape (backend API + workers + consumer + infra in containers).
cp sample.env .env
docker compose -f docker-compose.yml up --buildRun migrations (if needed):
docker compose -f docker-compose.yml run --rm backend uv run alembic upgrade headOpen:
- API docs:
http://localhost:8000/docs - MinIO console:
http://localhost:9001 - RabbitMQ UI:
http://localhost:15672(when exposed in your compose)
Run workers separately as needed:
uv run python -m app.workers --job dispatch_outbox_eventsWorkers also support one-shot execution via --once, so they can run as scheduled jobs (for example Kubernetes CronJobs) instead of only long-running loops:
uv run python -m app.workers --job dispatch_outbox_events --onceRun broker consumer (only when broker is enabled, i.e. broker_driver is not none):
uv run python -m app.consumerIn .env:
outbox_dispatch_strategy=direct|brokerbroker_driver=none|rabbitmqbroker_url=amqp://...- messenger credentials (
telegram_*,whatsapp_*) - storage and infra values (database, redis, s3/minio)
- Understand how enterprise-style backend boundaries look in practice.
- Practice event-driven workflows with outbox + workers + optional bus.
- Compare simple and advanced import pipelines side by side.
- Keep experimenting without losing architectural clarity.
This codebase is intentionally a playground.
It is where different backend concepts are tried in real code, compared against each other, and incrementally refactored. Some flows are intentionally overlapping (for example CSV worker path vs import pipeline) because the point is to explore trade-offs and learn by building.
This is a fun learning lab first, production template second 🚀
Think of this project as a lab for:
- architecture experiments
- event-driven workflow design
- integration patterns for external messaging systems
- practical async + worker reliability patterns
- Python requirement is
>=3.13. - Uses
uvfor dependency/project management.