Python SDK
The official Python SDK for Sent 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 sentdmpoetry add sentdmuv pip install sentdmQuick Start
Initialize the client
from sent_dm import SentDm
client = SentDm() # Uses SENT_DM_API_KEY env var by defaultSend 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 Code | Error Type |
|---|---|
| 400 | BadRequestError |
| 401 | AuthenticationError |
| 403 | PermissionDeniedError |
| 404 | NotFoundError |
| 422 | UnprocessableEntityError |
| 429 | RateLimitError |
| >=500 | InternalServerError |
| N/A | APIConnectionError |
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=infoOr to debug for more verbose logging.
Source & Issues
- Version: 0.8.0
- GitHub: sentdm/sent-dm-python
- PyPI: sentdm
- Issues: Report a bug
Getting Help
- Documentation: API Reference
- Troubleshooting: Common Issues
- Support: Email support@sent.dm with your request ID