A backend portfolio service for preparing and scheduling message campaigns. It models recipients, campaign runs and per-recipient delivery state.
- Validate campaign dates, daily sending windows and recipient criteria.
- Calculate planned sending times in each recipient's time zone and store them in UTC.
- Use explicit campaign/run/message states and reject repeated starts unless a resend is requested.
- Run the API, migrations, Celery worker and beat alongside PostgreSQL and Redis in Compose.
python -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements-dev.txt
python scripts/demo.pyThe demo obtains a JWT, creates synthetic recipients, starts a campaign through the API, runs eager Celery tasks and prints delivery statistics. It uses a disposable SQLite database and a simulated provider. It removes the database on exit. No Redis, PostgreSQL or external account is needed.
cp .env.example .env
# Configure DJANGO_SECRET_KEY and local database credentials.
docker compose up --buildpython -m pip install -r requirements-dev.txt
python -m pytest
ruff check .
black --check .API: http://localhost:8000/api/. Obtain JWT credentials from /api/token/ using an existing Django user; create one with docker compose exec api python manage.py createsuperuser. The local test suite uses SQLite unless DATABASE_URL is set; the existing CI also exercises PostgreSQL. Worker and beat must both run for scheduled processing.
Requests require a bearer token. The route is /api/campaigns/.
POST /api/campaigns/
Authorization: Bearer <access_token>
Content-Type: application/json
{
"start_datetime": "2030-01-01T10:00:00Z",
"end_datetime": "2030-01-01T18:00:00Z",
"time_interval_start": "10:00:00",
"time_interval_end": "18:00:00",
"text_message": "Hello",
"tag": "vip",
"client_filter": {"tags": ["vip"]}
}Creating a campaign automatically schedules its first run. Explicit starts use POST /api/campaigns/<id>/start/; inspect its statistics at GET /api/campaigns/<id>/stats/. Client records are managed under /api/clients/. See api/urls.py for the route list and api/serializers.py for validation rules.
Uses Python 3.11+ and Django 5.2 LTS. Regression tests cover daily windows crossing midnight, malformed recipient filters, invalid timezone names and broker publication failures. A failed broker publish returns the message to PENDING for a later dispatcher pass.
The worker atomically claims QUEUED → SENDING before contacting the provider, so concurrent workers do not enter the same send. A transient provider failure keeps the run active during the three retries; only exhausted retries become terminal. Late deliveries recheck the recipient's daily window. force_resend is available after a run ends; it cannot overlap an active run.
Read the delivery design and failure boundaries, then the regression tests. CI also runs a two-connection PostgreSQL concurrency test and the complete demo.
The provider adapter in api/services.py simulates delivery. A real integration must define provider-side idempotency, request timeouts and delivery reconciliation; the demo does not promise exactly-once external delivery.