PyTexas's communications dispatch service, powered by Temporal. It currently runs two workflows:
- Pretix orders: listens for pretix webhook notifications and posts formatted order summaries to a Discord channel.
- Monthly meetup dispatch: announces a booked meetup across channels; creates the Discord scheduled event and posts the asset handoff to the marketing channel and a setup summary to the organizers channel.
Started from the CLI by the meetup repo's
/meetup-updateautomation.
When a new order is placed in pretix, this service:
- Receives the webhook POST from pretix
- Fetches the full order details from the pretix REST API
- Formats a Discord embed with order code, total, buyer email, and purchased products
- Sends the embed to a Discord channel via webhook
The pipeline is orchestrated by Temporal, which provides automatic retries and durable execution. If Discord is temporarily unavailable, the notification is delivered when it recovers.
flowchart LR
pretix -- "webhook POST" --> FastAPI
FastAPI -- "starts workflow" --> Temporal
Temporal --> fetch["Fetch order from pretix"]
fetch --> format["Format embed"]
format --> send["Send to Discord"]
Services (all run via Docker Compose):
| Service | Description |
|---|---|
caddy |
Reverse proxy, automatic HTTPS via Let's Encrypt |
temporal |
Temporal dev server with persistent SQLite |
worker |
Temporal worker executing activities |
web |
FastAPI app receiving pretix webhooks |
Source layout:
src/pretix_discord/
├── api.py # FastAPI app (POST /webhook, GET /health)
├── config.py # Settings loaded from environment variables
├── discord_activities.py # Format embed, send Discord webhook
├── main.py # FastAPI/uvicorn entrypoint
├── models.py # All dataclasses (orders, embeds, inputs)
├── pretix_activities.py # Fetch and parse pretix orders
├── worker.py # Temporal worker entrypoint (registers both workflows)
└── workflow.py # Workflow: fetch -> format -> send
src/meetup_dispatch/
├── activities.py # Webhook posts, Discord event creation, message formatting
├── config.py # Meetup settings loaded from environment variables
├── models.py # Dispatch input, activity inputs, result
└── workflow.py # Workflow: create event -> post marketing -> post organizers
The meetup workflow is started manually (by the meetup repo's automation) once the month's assets exist:
temporal workflow start \
--task-queue pretix-discord \
--type MonthlyMeetupDispatch \
--workflow-id "meetup-dispatch-2026-08" \
--input '{"month": "August 2026",
"date_display": "Tuesday, August 4 at 8:00 PM Central",
"start_time_utc": "2026-08-05T01:00:00Z",
"end_time_utc": "2026-08-05T02:00:00Z",
"talk_title": "...", "speaker_name": "...", "promo_blurb": "...",
"canva_link": "...", "card_png_link": "...", "run_of_show_link": "...",
"attendance_form_link": "...", "questions_form_link": "...",
"still_manual": "Canva page title rename, meetup.com event"}'Inputs are links and strings only, never file bytes: Temporal caps a payload at 2MB, so the card image is passed as a Drive link.
The Discord event is created as an external event with the location text "PyTexas Stage" (stage-linked events have an audio quality bug).
If PYTEXAS_DISCORD_BOT_TOKEN is not configured, the event is skipped with a note and both webhook posts still go out with a TBD event link.
- A server with Docker installed
- A domain name pointed at the server (for HTTPS)
- A pretix API token (docs)
- A Discord webhook URL (docs)
Copy the example environment file and fill in your values:
cp .env.example .env| Variable | Description |
|---|---|
PRETIX_API_TOKEN |
API token from your pretix organizer account |
DISCORD_WEBHOOK_URL |
Full Discord webhook URL for the pretix order channel |
DOMAIN |
Public domain for this service (used by Caddy for TLS) |
PYTEXAS_MARKETING_WEBHOOK |
Webhook URL for the marketing channel (meetup dispatch) |
PYTEXAS_MEETUP_WEBHOOK |
Webhook URL for the meetup organizers channel (meetup dispatch) |
| Variable | Default | Description |
|---|---|---|
PYTEXAS_DISCORD_BOT_TOKEN |
unset | Bot token with Manage Events; event creation is skipped without it |
PYTEXAS_GUILD_ID |
1012382914035597372 |
PyTexas Discord guild ID |
| Variable | Default | Description |
|---|---|---|
PRETIX_BASE_URL |
https://pretix.eu/api/v1 |
pretix API base URL |
TEMPORAL_ADDRESS |
temporal:7233 |
Temporal server address |
TEMPORAL_NAMESPACE |
default |
Temporal namespace |
TEMPORAL_TASK_QUEUE |
pretix-discord |
Temporal task queue name |
Any VPS with Docker works. On a fresh Ubuntu/Debian server:
curl -fsSL https://get.docker.com | shgit clone https://github.com/pytexas/dispatch.git
cd dispatch
cp .env.example .env
# Edit .env with your valuesCreate an A record for your domain pointing to the server's IP address. Caddy needs this to provision the TLS certificate.
docker compose up -dCaddy will automatically obtain a Let's Encrypt certificate on the first request. The services will restart automatically if the server reboots.
In your pretix organizer settings:
- Go to Settings > Webhooks
- Add a new webhook with the URL:
https://YOUR_DOMAIN/webhook - Select the Order placed event
- Save
Check that the service is running:
curl https://YOUR_DOMAIN/health
# {"status":"ok"}Check the Temporal UI at http://YOUR_SERVER_IP:8233 to monitor workflows.
uv syncjust checkThis runs formatting (ruff), tests (pytest), linting (ruff), and type checking (mypy strict).
just fmt # Format code
just test # Run tests
just lint # Lint code
just typecheck # Type check (mypy --strict)With Docker Compose running locally:
curl -X POST http://localhost:8000/webhook \
-H "Content-Type: application/json" \
-d '{
"notification_id": 99999,
"organizer": "your-organizer",
"event": "your-event",
"code": "XXXXX",
"action": "pretix.event.order.placed"
}'The workflow will appear in the Temporal UI. If the order code is valid and your tokens are configured, a Discord message will appear in the target channel.
- Health check:
GET /healthreturns{"status": "ok"} - Temporal UI: Port 8233 — view workflow history, retries, and failures
- Docker logs:
docker compose logs -f workerto watch activity execution
MIT