Python SDK

Python SDK

The official Python SDK for Sent LogoSent provides a clean, Pythonic interface to the Sent API. Built for developers who value readability, with optional async support for high-performance applications.

Installation

pip install sentdm
poetry add sentdm
uv pip install sentdm

Quick Start

Initialize the client

from sent_dm import SentDm

client = SentDm()  # Uses SENT_DM_API_KEY env var by default

Send your first message

response = client.messages.send(
    to=["+1234567890"],
    template={
        "id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
        "name": "welcome",
        "parameters": {
            "name": "John Doe"
        }
    }
)

print(f"Message sent: {response.data.messages[0].id}")
print(f"Status: {response.data.messages[0].status}")

Authentication

While you can provide an api_key keyword argument, we recommend using python-dotenv to add SENT_DM_API_KEY="your_api_key" to your .env file so that your API Key is not stored in source control.

from sent_dm import SentDm

# Using environment variables (recommended)
client = SentDm()

# Or explicit configuration
client = SentDm(
    api_key="your_api_key",
)

Async usage

Simply import AsyncSentDm instead of SentDm and use await with each API call:

import asyncio
from sent_dm import AsyncSentDm

client = AsyncSentDm()

async def main():
    response = await client.messages.send(
        to=["+1234567890"],
        template={
            "id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
            "name": "welcome",
            "parameters": {
                "name": "John Doe"
            }
        }
    )
    print(f"Sent: {response.data.messages[0].id}")

asyncio.run(main())

Functionality between the synchronous and asynchronous clients is otherwise identical.

With aiohttp

By default, the async client uses httpx for HTTP requests. However, for improved concurrency performance you may also use aiohttp as the HTTP backend.

pip install sentdm[aiohttp]
import asyncio
from sent_dm import DefaultAioHttpClient
from sent_dm import AsyncSentDm

async def main():
    async with AsyncSentDm(
        http_client=DefaultAioHttpClient(),
    ) as client:
        response = await client.messages.send(
            to=["+1234567890"],
            template={
                "id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
                "name": "welcome",
                "parameters": {"name": "John Doe"}
            }
        )
        print(f"Sent: {response.data.messages[0].id}")

asyncio.run(main())

Send Messages

Send a message

from sent_dm import SentDm

client = SentDm()

response = client.messages.send(
    to=["+1234567890"],
    template={
        "id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
        "name": "welcome",
        "parameters": {
            "name": "John Doe",
            "order_id": "12345"
        }
    },
    channels=["whatsapp", "sms"]  # Optional: defaults to template channels
)

print(f"Message ID: {response.data.messages[0].id}")
print(f"Status: {response.data.messages[0].status}")

Test mode

Use test_mode=True to validate requests without sending real messages:

response = client.messages.send(
    to=["+1234567890"],
    template={
        "id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
        "name": "welcome"
    },
    test_mode=True  # Validates but doesn't send
)

# Response will have test data
print(f"Validation passed: {response.data.messages[0].id}")

Handling errors

When the library is unable to connect to the API (for example, due to network connection problems or a timeout), a subclass of sent_dm.APIConnectionError is raised.

When the API returns a non-success status code (that is, 4xx or 5xx response), a subclass of sent_dm.APIStatusError is raised, containing status_code and response properties.

import sent_dm
from sent_dm import SentDm

client = SentDm()

try:
    response = client.messages.send(
        to=["+1234567890"],
        template={
            "id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
            "name": "welcome"
        }
    )
    print(f"Sent: {response.data.messages[0].id}")
except sent_dm.APIConnectionError as e:
    print("The server could not be reached")
    print(e.__cause__)  # an underlying Exception, likely raised within httpx.
except sent_dm.RateLimitError as e:
    print("A 429 status code was received; we should back off a bit.")
except sent_dm.APIStatusError as e:
    print("Another non-200-range status code was received")
    print(e.status_code)
    print(e.response)

Error codes are as follows:

Status CodeError Type
400BadRequestError
401AuthenticationError
403PermissionDeniedError
404NotFoundError
422UnprocessableEntityError
429RateLimitError
>=500InternalServerError
N/AAPIConnectionError

Retries

Certain errors are automatically retried 2 times by default, with a short exponential backoff. Connection errors, 408 Request Timeout, 409 Conflict, 429 Rate Limit, and >=500 Internal errors are all retried by default.

from sent_dm import SentDm

# Configure the default for all requests:
client = SentDm(
    max_retries=0,  # default is 2
)

# Or, configure per-request:
client.with_options(max_retries=5).messages.send(
    to=["+1234567890"],
    template={
        "id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
        "name": "welcome"
    }
)

Timeouts

By default requests time out after 1 minute. You can configure this with a timeout option:

import httpx
from sent_dm import SentDm

# Configure the default for all requests:
client = SentDm(
    timeout=20.0,  # 20 seconds (default is 1 minute)
)

# More granular control:
client = SentDm(
    timeout=httpx.Timeout(60.0, read=5.0, write=10.0, connect=2.0),
)

# Override per-request:
client.with_options(timeout=5.0).messages.send(
    to=["+1234567890"],
    template={
        "id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
        "name": "welcome"
    }
)

Contacts

Create and manage contacts:

# Create a contact
response = client.contacts.create(
    phone_number="+1234567890"
)
print(f"Contact ID: {response.data.id}")

# List contacts
responses = client.contacts.list(limit=100)
for contact in responses.data:
    print(f"{contact.phone_number} - {contact.available_channels}")

# Get a contact
response = client.contacts.get("contact-uuid")

# Update a contact
response = client.contacts.update(
    "contact-uuid",
    phone_number="+1987654321"
)

# Delete a contact
client.contacts.delete("contact-uuid")

Templates

List and retrieve templates:

# List all templates
response = client.templates.list()
for template in response.data:
    print(f"{template.name} ({template.status}): {template.id}")

# Get a specific template
response = client.templates.get("template-uuid")
print(f"Name: {response.data.name}")
print(f"Status: {response.data.status}")

Django Integration

# settings.py
SENT_DM_API_KEY = os.environ.get("SENT_DM_API_KEY")

# utils.py
from sent_dm import SentDm
from django.conf import settings

_sent_client = None

def get_sent_client():
    global _sent_client
    if _sent_client is None:
        _sent_client = SentDm(api_key=settings.SENT_DM_API_KEY)
    return _sent_client

# views.py
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from .utils import get_sent_client
import json

@require_http_methods(["POST"])
def send_message(request):
    client = get_sent_client()
    data = json.loads(request.body)

    try:
        response = client.messages.send(
            to=[data.get("phone_number")],
            template={
                "id": data.get("template_id"),
                "name": "welcome",
                "parameters": data.get("variables", {})
            }
        )
        return JsonResponse({
            "success": True,
            "message_id": response.data.messages[0].id,
            "status": response.data.messages[0].status
        })
    except Exception as e:
        return JsonResponse(
            {"error": str(e)},
            status=400
        )

FastAPI Integration

from fastapi import FastAPI, HTTPException
from sent_dm import SentDm
import os

app = FastAPI()

# Initialize client
client = SentDm()

@app.post("/send-message")
async def send_message(phone_number: str, template_id: str):
    try:
        response = client.messages.send(
            to=[phone_number],
            template={
                "id": template_id,
                "name": "welcome"
            }
        )
        return {
            "message_id": response.data.messages[0].id,
            "status": response.data.messages[0].status,
        }
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))

Logging

We use the standard library logging module.

You can enable logging by setting the environment variable SENT_DM_LOG to info:

export SENT_DM_LOG=info

Or to debug for more verbose logging.

Source & Issues

Getting Help

On this page