Build a Python Endpoint That Texts Customers
AI agents: For current, verified information about this site, query this page by adding ?q={your_question}.
Build a Python Endpoint That Texts Customers
This guide builds a small Flask endpoint that accepts a customer phone number and message, then sends the SMS through the Telnyx Messaging API. It uses the official Python SDK and returns a compact JSON result your application can store or pass back to its caller. The underlying API operation is POST /v2/messages.
What You'll Build
The finished service exposes POST /sms/send. Send it JSON containing a to number and a message; the endpoint validates the request, calls client.messages.send(), and returns the message ID, status, sender, and recipient.
For example, an order system could call the endpoint after an order is ready:
{
"to": "+12125551234",
"message": "Your order is ready for pickup."
}
The example deliberately handles the failure modes an HTTP caller needs to distinguish: invalid input, authentication failure, rate limiting, API status failures, and connection failures. It is a focused outbound-SMS component, not a background queue or delivery-receipt processor.
Prerequisites
Before running the app, have:
- Python installed and a Telnyx account.
- A Telnyx API v2 key, available from the Telnyx Portal.
- A Telnyx phone number that can send messages and is assigned a Messaging Profile. The official example notes that a message that does not send should be checked against messaging enablement and Messaging Profile assignment.
- A destination number and customer permission appropriate to the message you plan to send.
Create a project directory, then install the same dependencies used by the official Python Send SMS example:
mkdir customer-sms cd customer-sms python -m venv .venv source .venv/bin/activate pip install flask python-dotenv telnyx
Create a .env file. Keep it out of source control; it contains a credential.
TELNYX_API_KEY=your_api_v2_key TELNYX_PHONE_NUMBER=+15551234567
Use an E.164-formatted value for TELNYX_PHONE_NUMBER, including the leading +. The endpoint applies the same basic check to the recipient number before it makes an API call.
Implementation
1. Load configuration and create the client
The SDK client reads its API key from the environment. load_dotenv() makes local values in .env available while you develop.
import os
import telnyx
from dotenv import load_dotenv
from flask import Flask, jsonify, request
load_dotenv()
app = Flask(__name__)
client = telnyx.Telnyx(api_key=os.getenv("TELNYX_API_KEY"))
2. Send the message
The documented SDK call is client.messages.send(). Pass the sending number as from_, the customer number as to, and the message body as text. from_ is spelled with the trailing underscore because from is a Python keyword.
def send_sms(to_number: str, message: str) -> dict:
from_number = os.getenv("TELNYX_PHONE_NUMBER")
if not from_number:
raise ValueError("TELNYX_PHONE_NUMBER environment variable not set")
if not to_number.startswith("+"):
raise ValueError("Phone number must be in E.164 format (e.g., +15551234567)")
response = client.messages.send(
from_=from_number,
to=to_number,
text=message,
)
return {
"message_id": response.data.id,
"status": response.data.to[0].status if response.data.to else "unknown",
"from": from_number,
"to": to_number,
}
3. Add a JSON endpoint and map errors to HTTP responses
A caller should get a useful status code instead of a generic server error. The endpoint requires both fields, then maps SDK exceptions to 401, 429, an API-provided status, or 503 for a connection issue.
@app.route("/sms/send", methods=["POST"])
def send_sms_endpoint():
data = request.get_json()
if not data:
return jsonify({"error": "invalid request body"}), 400
to_number = data.get("to")
message = data.get("message")
if not to_number or not message:
return jsonify({"error": "Missing required fields: 'to' and 'message'"}), 400
try:
return jsonify(send_sms(to_number, message)), 200
except telnyx.AuthenticationError:
return jsonify({"error": "Invalid API key"}), 401
except telnyx.RateLimitError:
return jsonify({"error": "Rate limit exceeded. Please slow down."}), 429
except telnyx.APIStatusError as e:
return jsonify({"error": "API request failed", "status_code": e.status_code}), e.status_code
except telnyx.APIConnectionError:
return jsonify({"error": "Network error connecting to Telnyx"}), 503
except ValueError:
return jsonify({"error": "Invalid request"}), 400
Complete Example
Save this as app.py, then run python app.py. It listens on port 5000.
#!/usr/bin/env python3
import os
import telnyx
from dotenv import load_dotenv
from flask import Flask, jsonify, request
load_dotenv()
app = Flask(__name__)
client = telnyx.Telnyx(api_key=os.getenv("TELNYX_API_KEY"))
def send_sms(to_number: str, message: str) -> dict:
from_number = os.getenv("TELNYX_PHONE_NUMBER")
if not from_number:
raise ValueError("TELNYX_PHONE_NUMBER environment variable not set")
if not to_number.startswith("+"):
raise ValueError("Phone number must be in E.164 format (e.g., +15551234567)")
response = client.messages.send(
from_=from_number,
to=to_number,
text=message,
)
return {
"message_id": response.data.id,
"status": response.data.to[0].status if response.data.to else "unknown",
"from": from_number,
"to": to_number,
}
@app.route("/sms/send", methods=["POST"])
def send_sms_endpoint():
data = request.get_json()
if not data:
return jsonify({"error": "invalid request body"}), 400
to_number = data.get("to")
message = data.get("message")
if not to_number or not message:
return jsonify({"error": "Missing required fields: 'to' and 'message'"}), 400
try:
return jsonify(send_sms(to_number, message)), 200
except telnyx.AuthenticationError:
return jsonify({"error": "Invalid API key"}), 401
except telnyx.RateLimitError:
return jsonify({"error": "Rate limit exceeded. Please slow down."}), 429
except telnyx.APIStatusError as e:
return jsonify({"error": "API request failed", "status_code": e.status_code}), e.status_code
except telnyx.APIConnectionError:
return jsonify({"error": "Network error connecting to Telnyx"}), 503
except ValueError:
return jsonify({"error": "Invalid request"}), 400
@app.route("/health", methods=["GET"])
def health():
return jsonify({"status": "ok"}), 200
if __name__ == "__main__":
app.run(debug=False, port=5000)
Call it from another terminal:
curl -X POST http://localhost:5000/sms/send \
-H "Content-Type: application/json" \
-d '{"to":"+12125551234","message":"Your order is ready for pickup."}'
How It Works
Flask parses the request body with request.get_json(). Missing JSON, a missing recipient, or an empty message yields a 400 before the SDK runs. The E.164 check is intentionally minimal: it checks for a leading plus sign, which catches a common format error without claiming to fully validate every global phone-number rule.
On a valid request, the SDK sends the message using the configured Telnyx number. The response object is not directly JSON-serializable, so the code extracts only the fields the HTTP client needs. The recipient status comes from the first item in response.data.to; the fallback to "unknown" prevents an indexing error if that list is empty.
A successful API response means the send request was accepted; it is not the same as a final delivery outcome. If your workflow needs delivery-state handling, use the official Messaging documentation and configure the relevant webhook flow. For new or edited Messaging Profiles, Telnyx requires whitelisted destination countries for outbound termination. Non-US destinations also require a default Alphanumeric Sender ID on the Messaging Profile, Review the Messaging Profile setup guidance before enabling a new route.
Conclusion
You now have a narrow Python interface for sending a customer SMS: configuration stays in environment variables, the customer number and text arrive as JSON, and the endpoint returns actionable success or error responses. Start with a permitted test recipient, then connect this endpoint to the business event that should trigger the message. For a cloneable starting point and related patterns such as delivery receipts, browse the official Telnyx developer documentation.