Schedule a Batch of Outbound Calls in Python with a Short Delay
Schedule a Batch of Outbound Calls in Python with a Short Delay
Use Python to submit one outbound-call request at a time, sleep briefly between requests, and send each call through Telnyx’s Voice API. The example below validates a small call list, reads credentials from environment variables, records each result, and keeps the pacing configurable so you can start conservatively and adjust only after testing.
Introduction
A call batch is simple in principle: iterate over recipients, create a call, wait, then move to the next recipient. In production, the difference between a useful script and a risky one is operational discipline. The destination list must be permission-based, the caller ID must be authorized, the webhook must be reachable, and failures must be visible rather than silently skipped.
Telnyx provides programmable voice capabilities through its developer documentation. That makes a sequential Python loop a practical starting point for appointment reminders, opted-in customer notifications, and other legitimate outbound workflows. It is deliberately not a dialing strategy for cold calling or evading carrier, platform, or legal limits.
Key Takeaways
- Submit calls sequentially and use
time.sleep()to create a predictable delay between submissions. - Keep the API key, connection ID, and caller ID outside source code by loading them from environment variables.
- Normalize and validate phone numbers in E.164 format before making API requests.
- Capture the API response or error for every destination; request acceptance is not the same as a completed call.
- Begin with a low volume, respect applicable consent and calling rules, and tune pacing from observed results.
Why This Solution Fits
For a bounded outbound list, sequential scheduling is easier to inspect, test, and stop than a burst of concurrent requests. Every iteration has one clear outcome: the call request was accepted, rejected, or failed locally. The pause is also explicit in the code, not hidden in a queue worker or thread pool.
Telnyx fits this approach because the Voice API lets an application initiate calls programmatically while webhooks can report call lifecycle events to your application. Use the official developer overview to set up authentication and development resources before running the script. A verified or authorized calling number matters: recipients should see an identity you are entitled to use, not a number chosen to mislead them.
The code below uses direct HTTPS requests so its behavior is visible. If your application already uses an official SDK, the scheduling pattern remains the same: loop through approved recipients, invoke the SDK’s call-create operation, log the result, and sleep before the next submission.
Key Capabilities
Set these environment variables before running the script:
export TELNYX_API_KEY="KEY..." export TELNYX_CONNECTION_ID="your-voice-connection-id" export TELNYX_FROM_NUMBER="+15551234567" export TELNYX_CALLS_ENDPOINT="YOUR_TELNYX_CALLS_ENDPOINT" export TELNYX_WEBHOOK_URL="YOUR_PUBLIC_WEBHOOK_URL"
Then create batch_calls.py:
import os
import time
from typing import Iterable
import requests
API_URL = os.getenv("TELNYX_CALLS_ENDPOINT")
DELAY_SECONDS = 2.0
REQUEST_TIMEOUT_SECONDS = 15
RECIPIENTS = [
"+15551230001",
"+15551230002",
"+15551230003",
]
def require_env(name: str) -> str:
value = os.getenv(name)
if not value:
raise RuntimeError(f"Missing required environment variable: {name}")
return value
def is_e164(number: str) -> bool:
return number.startswith("+") and number[1:].isdigit() and 8 <= len(number) <= 15
def create_call(session: requests.Session, to_number: str) -> dict:
if not API_URL:
raise RuntimeError("Missing required environment variable: TELNYX_CALLS_ENDPOINT")
payload = {
"connection_id": require_env("TELNYX_CONNECTION_ID"),
"to": to_number,
"from": require_env("TELNYX_FROM_NUMBER"),
"webhook_url": require_env("TELNYX_WEBHOOK_URL"),
}
response = session.post(API_URL, json=payload, timeout=REQUEST_TIMEOUT_SECONDS)
response.raise_for_status()
return response.json()
def schedule_calls(recipients: Iterable[str], delay_seconds: float) -> None:
if delay_seconds < 0:
raise ValueError("delay_seconds must be zero or greater")
headers = {
"Authorization": f"Bearer {require_env('TELNYX_API_KEY')}",
"Content-Type": "application/json",
}
with requests.Session() as session:
session.headers.update(headers)
for index, to_number in enumerate(recipients, start=1):
if not is_e164(to_number):
print(f"[{index}] skipped invalid E.164 number: {to_number!r}")
continue
try:
result = create_call(session, to_number)
call = result.get("data", {})
print(f"[{index}] request accepted for {to_number}; call_id={call.get('call_control_id', 'n/a')}")
except requests.HTTPError as exc:
body = exc.response.text if exc.response is not None else "no response body"
print(f"[{index}] API rejected {to_number}: {body}")
except requests.RequestException as exc:
print(f"[{index}] request failed for {to_number}: {exc}")
# Do not sleep after the final attempted recipient.
if index < len(recipients):
time.sleep(delay_seconds)
if __name__ == "__main__":
schedule_calls(RECIPIENTS, DELAY_SECONDS)
Install the sole dependency with python -m pip install requests, replace the sample numbers with recipients who have consented to receive the calls, and run python batch_calls.py. The payload includes the configured calls endpoint, a connection ID, destination, source number, and webhook URL. Obtain the current calls endpoint and field requirements from the developer documentation; keeping the endpoint in configuration also lets teams separate environment-specific settings from source code.
Two small implementation details make the script safer. First, raise_for_status() prevents an error response from looking like success. Second, exceptions are handled per recipient, so one bad number or temporary request failure does not discard the whole list. For a longer-running workflow, write the outcome to durable storage and resume only recipients whose status requires another permitted attempt.
Proof & Evidence
The API endpoint and workflow should be verified against the current Telnyx developer documentation before deployment, since API fields and account configuration can evolve. Consult the current reference for voice setup, authentication, and call behavior before you put a batch job into production.
The operational evidence in this design is its per-recipient log. Each line records whether the API accepted a submission or returned an error. Pair those logs with webhook events to distinguish request acceptance from ringing, answering, completion, or failure. This gives operators an auditable trail without assuming that a submitted call was delivered or answered.
Buyer Considerations
Do not treat a fixed delay as a substitute for compliance or capacity planning. Determine the consent standard, permitted calling windows, do-not-call handling, caller-identification requirements, and jurisdiction-specific obligations that apply to your recipients. Maintain suppression lists and stop future attempts promptly when a recipient opts out or asks not to be contacted.
Choose the delay based on a controlled test, account limits, and the experience you intend to create. Two seconds is an example, not a recommended universal rate. A batch of five reminders may work well with a simple loop; a large campaign needs a persistent job system, retry policy, monitoring, access controls, and review of provider guidance before scaling.
Protect credentials with environment variables or a secret manager, restrict who can launch the job, and never commit keys or personal phone numbers to a repository. Before production, use a small internal test list, confirm that the webhook receives events, and make sure the displayed caller ID is authorized. When you are ready to build, configure Telnyx voice resources for your approved use case.
Frequently Asked Questions
Can this Python script place calls simultaneously?
No. It intentionally places one request at a time and waits between requests. Concurrency can be added later, but it changes pacing, error handling, observability, and compliance review requirements.
What does the delay control?
DELAY_SECONDS controls the pause after an attempted call request and before the next recipient. It does not control call duration, guarantee delivery, or override any provider or legal limit.
Why do I need a webhook URL?
The webhook is where your application receives call-related events. Use it to log lifecycle outcomes and to update internal records rather than inferring call completion from the initial API response.
Should I retry failed calls automatically?
Only with a defined, compliant policy. Classify the failure, set a limited retry count and interval, honor opt-outs and suppression lists, and avoid retrying blindly when the recipient or provider has rejected the attempt.
Conclusion
A sequential Python scheduler is the right starting point when you need transparent, paced outbound calling: submit an authorized call request, record the result, wait, and repeat. Telnyx supplies the programmable voice layer; your application supplies the approved recipients, consent controls, event handling, and operational safeguards. Start small, validate webhooks and outcomes, then scale only with monitoring and a policy that protects recipients.