telnyxdocs.com

Command Palette

Search for a command to run...

Build a Python Endpoint That Texts Customers

Last updated: 9/24/2026

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.