bulk outbound calls with automatic transcription. places calls via Telnyx, plays a prompt, records the response, downloads the audio, and runs it through OpenAI Whisper. results stream to a TSV file as they complete.
cli-bulk-caller call numbers.txtalso works as a standalone transcriber for local audio files — no Telnyx needed.
numbers.txt
→ telnyx.Call.create() per number (thread pool, concurrent)
→ call answered → playback_start(audio_url) + record_start()
→ Telnyx fires webhook: call.recording.saved
→ download recording → OpenAI Whisper transcription
→ append result to TSV
a Flask server handles the Telnyx webhook lifecycle. the whole pipeline is event-driven — calls fan out, webhooks come back, transcriptions stream to disk. nothing is batched at the end.
- concurrent outbound calls — reads E.164 numbers from a file, fans out via thread pool
- automatic playback + recording — plays your audio prompt on answer, starts recording simultaneously
- webhook-driven pipeline — Flask server reacts to Telnyx events (
call.answered,call.hangup,call.recording.saved) - Whisper transcription — downloads recording, sends to
whisper-1, writes result immediately - standalone file transcription — skip Telnyx entirely, transcribe local files or directories
- exponential backoff — retries transcription up to 10 times with
delay * 2^attempt - thread-safe incremental output — results written as they complete, not held in memory
- multi-format output — TSV (default), CSV, or newline-delimited JSON
git clone https://github.com/yigitkonur/cli-bulk-caller.git
cd cli-bulk-caller
pip install .this registers two CLI commands: cli-bulk-caller and tt (short alias).
copy the example and fill it in:
cp .env.example .env# required for call mode
TELNYX_API_KEY=your_telnyx_api_key
TELNYX_CONNECTION_ID=your_connection_id
TELNYX_FROM_NUMBER=+1234567890
AUDIO_URL=https://example.com/prompt.mp3
# required always
OPENAI_API_KEY=your_openai_api_key
# optional
WEBHOOK_HOST=0.0.0.0
WEBHOOK_PORT=5000
OUTPUT_FILE=results.tsv
MAX_WORKERS=5
RECORDING_FORMAT=mp3
RECORDING_CHANNELS=single
MAX_RETRIES=10
RETRY_DELAY=2.0cli-bulk-caller call numbers.txt
cli-bulk-caller call numbers.txt --output calls.tsv --workers 10numbers.txt is one E.164 phone number per line.
cli-bulk-caller transcribe recording.mp3
cli-bulk-caller transcribe ./recordings/ --workers 3 --language ensupports .mp3, .wav, .m4a, .ogg, .flac, .webm, .mp4, .mpeg, .mpga.
cli-bulk-caller server --port 5000 --host 0.0.0.0 --output results.tsvcli-bulk-caller validateprints a checklist of which required env vars are set.
| flag | default | description |
|---|---|---|
--output / -o |
results.tsv |
output file path |
--workers / -w |
5 |
concurrent calls |
--no-server |
— | skip starting the webhook server |
| flag | default | description |
|---|---|---|
--output / -o |
transcriptions.tsv |
output file path |
--workers / -w |
3 |
concurrent transcriptions |
--language / -l |
auto-detect | BCP-47 language hint for Whisper (e.g. en, es, tr) |
| flag | default | description |
|---|---|---|
--port / -p |
5000 |
webhook server port |
--host / -h |
0.0.0.0 |
webhook server host |
--output / -o |
results.tsv |
output file path |
| method | path | description |
|---|---|---|
GET |
/health |
health check, returns active call count |
POST |
/webhook |
main Telnyx event receiver |
POST |
/webhook/call-recording-saved |
dedicated recording-saved handler |
GET |
/webhook/health |
blueprint health check |
the webhook server must be publicly reachable for Telnyx to deliver events. use ngrok, a reverse proxy, or deploy to a server with a public IP.
src/cli_bulk_caller/
__init__.py — package root
__main__.py — python -m support
app.py — Flask app factory + orchestrator
cli.py — Typer CLI (call, transcribe, server, validate)
config.py — settings dataclass, env loading, validation
exceptions.py — custom exception hierarchy
models.py — Call, TranscriptionResult, CallTranscriptionResult
services/
call_service.py — Telnyx call lifecycle + thread pool fan-out
transcription_service.py — Whisper interface, URL download, directory batch
output_service.py — thread-safe TSV/CSV/JSON writer
utils/
console.py — Rich terminal UI
logging.py — logging config
webhooks/
handlers.py — webhook dispatch + Flask blueprint
| variable | required | default | description |
|---|---|---|---|
TELNYX_API_KEY |
call mode | — | Telnyx API key |
TELNYX_CONNECTION_ID |
call mode | — | Telnyx connection/SIP trunk ID |
TELNYX_FROM_NUMBER |
call mode | — | caller ID (E.164) |
OPENAI_API_KEY |
always | — | OpenAI API key for Whisper |
AUDIO_URL |
call mode | — | URL of audio prompt played on answer |
WEBHOOK_HOST |
no | 0.0.0.0 |
Flask server host |
WEBHOOK_PORT |
no | 5000 |
Flask server port |
OUTPUT_FILE |
no | results.tsv |
output file path |
MAX_WORKERS |
no | 5 |
max concurrent calls/transcriptions |
RECORDING_FORMAT |
no | mp3 |
mp3 or wav |
RECORDING_CHANNELS |
no | single |
single or dual |
MAX_RETRIES |
no | 10 |
max Whisper retry attempts |
RETRY_DELAY |
no | 2.0 |
base delay (seconds) for exponential backoff |
MIT