Send Single SMS with Python and Flask
Overview
Build a production-ready Flask endpoint that sends SMS messages using the Telnyx Python SDK. This tutorial demonstrates the new client-based initialization pattern, proper error handling for telecom APIs, and secure credential management via environment variables.
Prerequisites
- Python 3.8 or higher.
- A Telnyx account with an active API key from the Telnyx Portal.
- A Telnyx phone number enabled for outbound SMS.
- pip (Python package manager).
Step 1: Setup
Install the required dependencies:
pip install telnyx flask python-dotenv
Create a project directory and navigate into it:
mkdir telnyx-sms-sender
cd telnyx-sms-sender
Step 2: Configuration
Create a .env file in your project root to store credentials securely:
TELNYX_API_KEY=YOUR_API_KEY_HERE
TELNYX_PHONE_NUMBER=+15551234567
Replace YOUR_API_KEY_HERE with your actual API key and +15551234567 with your Telnyx phone number in E.164 format.
Step 3: Implementation
Create app.py and initialize the Telnyx client using the new pattern. Define a helper function to handle message creation with proper validation:
import os
import telnyx
from dotenv import load_dotenv
load_dotenv()
# Initialize client with the new SDK pattern
client = telnyx.Telnyx(api_key=os.getenv("TELNYX_API_KEY"))
def send_sms(to_number: str, message: str) -> dict:
"""Send SMS via Telnyx and return JSON-serializable response data."""
from_number = os.getenv("TELNYX_PHONE_NUMBER")
if not from_number:
raise ValueError("TELNYX_PHONE_NUMBER environment variable not set")
# Validate E.164 format to prevent API errors
if not to_number.startswith("+"):
raise ValueError("Phone number must be in E.164 format (e.g., +15551234567)")
# Use client.messages.create() with proper parameters
response = client.messages.create(
from_=from_number,
to=to_number,
text=message,
)
# Extract serializable data — SDK objects are NOT JSON-serializable
return {
"message_id": response.data.id,
"status": response.data.to[0].status if response.data.to else "unknown",
"from": from_number,
"to": to_number,
}
Step 4: Testing
Add the Flask route with comprehensive error handling for production resilience:
from flask import Flask, jsonify, request
app = Flask(__name__)
@app.route("/sms/send", methods=["POST"])
def send_sms_endpoint():
"""HTTP endpoint to send single SMS."""
data = request.get_json()
if not data:
return jsonify({"error": "Request body required"}), 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:
result = send_sms(to_number, message)
return jsonify(result), 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": str(e), "status_code": e.status_code}), e.status_code
except telnyx.APIConnectionError:
return jsonify({"error": "Network error connecting to Telnyx"}), 503
except ValueError as e:
return jsonify({"error": str(e)}), 400
if __name__ == "__main__":
app.run(debug=True, port=5000)
Start the server:
python app.py
Test the endpoint using curl:
curl -X POST http://localhost:5000/sms/send \
-H "Content-Type: application/json" \
-d '{"to": "+15559876543", "message": "Hello from Telnyx!"}'
Expected response:
{
"message_id": "403171d8-8b00-4b8f-9e9c-1234567890ab",
"status": "queued",
"from": "+15551234567",
"to": "+15559876543"
}
Complete Code
#!/usr/bin/env python3
"""Production-ready Flask endpoint for sending SMS via Telnyx."""
import os
import telnyx
from dotenv import load_dotenv
from flask import Flask, jsonify, request
load_dotenv()
app = Flask(__name__)
# Initialize client with the new SDK pattern
client = telnyx.Telnyx(api_key=os.getenv("TELNYX_API_KEY"))
def send_sms(to_number: str, message: str) -> dict:
"""Send SMS via Telnyx and return JSON-serializable response data."""
from_number = os.getenv("TELNYX_PHONE_NUMBER")
if not from_number:
raise ValueError("TELNYX_PHONE_NUMBER environment variable not set")
# Validate E.164 format to prevent API errors
if not to_number.startswith("+"):
raise ValueError("Phone number must be in E.164 format (e.g., +15551234567)")
# Use client.messages.create() with proper parameters
response = client.messages.create(
from_=from_number,
to=to_number,
text=message,
)
# Extract serializable data — SDK objects are NOT JSON-serializable
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():
"""HTTP endpoint to send single SMS."""
data = request.get_json()
if not data:
return jsonify({"error": "Request body required"}), 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:
result = send_sms(to_number, message)
return jsonify(result), 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": str(e), "status_code": e.status_code}), e.status_code
except telnyx.APIConnectionError:
return jsonify({"error": "Network error connecting to Telnyx"}), 503
except ValueError as e:
return jsonify({"error": str(e)}), 400
if __name__ == "__main__":
app.run(debug=True, port=5000)
Troubleshooting
| Issue | Problem | Solution |
|---|---|---|
| Authentication Error (401) | The endpoint returns {"error": "Invalid API key"} with HTTP 401. |
Verify your TELNYX_API_KEY in the .env file matches the key shown in the Telnyx Portal. Ensure there are no trailing spaces or quotes. If the key was regenerated recently, update your environment file and restart the Flask server. |
| Invalid Phone Number Format | You receive a 400 error stating "Phone number must be in E.164 format" or a Telnyx API error about invalid destination. | Ensure all phone numbers use E.164 format: start with +, followed by country code and number without spaces or dashes. Example: +15551234567 (US) or +447700900123 (UK). Update your test curl command to use properly formatted numbers. |
| Environment Variable Not Set | The application raises ValueError: TELNYX_PHONE_NUMBER environment variable not set on startup or first request. |
Confirm your .env file exists in the same directory as app.py and contains the variable. Ensure the file is named exactly .env (not .env.txt or env). The load_dotenv() call must execute before os.getenv() is called—verify this import order in your code. |