sendly
Official Python SDK for the Sendly SMS API.
Installation
pip install sendly
Requirements
Quick Start
from sendly import Sendly
client = Sendly('sk_live_v1_your_api_key')
message = client.messages.send(
to='+15551234567',
text='Hello from Sendly!'
)
print(f'Message sent: {message.id}')
print(f'Status: {message.status}')
Prerequisites for Live Messaging
Before sending live SMS messages, you need:
-
Business Verification - Complete verification in the Sendly dashboard
- International: Instant approval (just provide Sender ID)
- US/Canada: Requires carrier approval (3-7 business days)
-
Credits - Add credits to your account
- Test keys (
sk_test_*) work without credits (sandbox mode)
- Live keys (
sk_live_*) require credits for each message
-
Live API Key - Generate after verification + credits
- Dashboard → API Keys → Create Live Key
Test vs Live Keys
| Test | sk_test_v1_* | No | No | Development, testing |
| Live | sk_live_v1_* | Yes | Yes | Production messaging |
Note: You can start development immediately with a test key. Messages to sandbox test numbers are free and don't require verification.
Features
- ✅ Full type hints (PEP 484)
- ✅ Sync and async clients
- ✅ Automatic retries with exponential backoff
- ✅ Rate limit handling
- ✅ Pydantic models for data validation
- ✅ Python 3.8+ support
Usage
Sending Messages
from sendly import Sendly
client = Sendly('sk_live_v1_xxx')
message = client.messages.send(
to='+15551234567',
text='Your verification code is: 123456'
)
message = client.messages.send(
to='+447700900123',
text='Hello from MyApp!',
from_='MYAPP'
)
Listing Messages
result = client.messages.list()
print(f'Found {result.count} messages')
result = client.messages.list(limit=10)
for msg in result.data:
print(f'{msg.to}: {msg.status}')
Getting a Message
message = client.messages.get('msg_xxx')
print(f'Status: {message.status}')
print(f'Delivered: {message.delivered_at}')
Rate Limit Information
client.messages.send(to='+1555...', text='Hello!')
rate_limit = client.get_rate_limit_info()
if rate_limit:
print(f'{rate_limit.remaining}/{rate_limit.limit} requests remaining')
print(f'Resets in {rate_limit.reset} seconds')
Async Client
For async/await support, use AsyncSendly:
import asyncio
from sendly import AsyncSendly
async def main():
async with AsyncSendly('sk_live_v1_xxx') as client:
message = await client.messages.send(
to='+15551234567',
text='Hello from async!'
)
print(message.id)
result = await client.messages.list(limit=10)
for msg in result.data:
print(f'{msg.to}: {msg.status}')
asyncio.run(main())
Configuration
from sendly import Sendly, SendlyConfig
client = Sendly(
api_key='sk_live_v1_xxx',
base_url='https://sendly.live/api',
timeout=60.0,
max_retries=5
)
config = SendlyConfig(
api_key='sk_live_v1_xxx',
timeout=60.0,
max_retries=5
)
client = Sendly(config=config)
Error Handling
The SDK provides typed exception classes:
from sendly import (
Sendly,
SendlyError,
AuthenticationError,
RateLimitError,
InsufficientCreditsError,
ValidationError,
NotFoundError,
)
client = Sendly('sk_live_v1_xxx')
try:
message = client.messages.send(
to='+15551234567',
text='Hello!'
)
except AuthenticationError as e:
print(f'Invalid API key: {e.message}')
except RateLimitError as e:
print(f'Rate limited. Retry after {e.retry_after} seconds')
except InsufficientCreditsError as e:
print(f'Need {e.credits_needed} credits, have {e.current_balance}')
except ValidationError as e:
print(f'Invalid request: {e.message}')
except NotFoundError as e:
print(f'Resource not found: {e.message}')
except SendlyError as e:
print(f'API error [{e.code}]: {e.message}')
Testing (Sandbox Mode)
Use a test API key (sk_test_v1_xxx) for testing:
from sendly import Sendly, SANDBOX_TEST_NUMBERS
client = Sendly('sk_test_v1_xxx')
print(client.is_test_mode())
message = client.messages.send(
to=SANDBOX_TEST_NUMBERS.SUCCESS,
text='Test message'
)
message = client.messages.send(
to=SANDBOX_TEST_NUMBERS.INVALID,
text='This will fail'
)
Available Test Numbers
+15550001234 | Instant success |
+15550001010 | Success after 10s delay |
+15550001001 | Fails: invalid_number |
+15550001002 | Fails: carrier_rejected (2s delay) |
+15550001003 | Fails: rate_limit_exceeded |
Pricing Tiers
from sendly import CREDITS_PER_SMS, SUPPORTED_COUNTRIES, PricingTier
print(CREDITS_PER_SMS[PricingTier.DOMESTIC])
print(CREDITS_PER_SMS[PricingTier.TIER1])
print(CREDITS_PER_SMS[PricingTier.TIER2])
print(CREDITS_PER_SMS[PricingTier.TIER3])
print(SUPPORTED_COUNTRIES[PricingTier.DOMESTIC])
print(SUPPORTED_COUNTRIES[PricingTier.TIER1])
Utilities
The SDK exports validation utilities:
from sendly import (
validate_phone_number,
get_country_from_phone,
is_country_supported,
calculate_segments,
)
validate_phone_number('+15551234567')
validate_phone_number('555-1234')
get_country_from_phone('+447700900123')
get_country_from_phone('+15551234567')
is_country_supported('GB')
is_country_supported('XX')
calculate_segments('Hello!')
calculate_segments('A' * 200)
Type Hints
The SDK is fully typed. Import types for your IDE:
from sendly import (
SendlyConfig,
SendMessageRequest,
Message,
MessageStatus,
ListMessagesOptions,
MessageListResponse,
RateLimitInfo,
PricingTier,
)
Context Manager
Both sync and async clients support context managers:
with Sendly('sk_live_v1_xxx') as client:
message = client.messages.send(to='+1555...', text='Hello!')
async with AsyncSendly('sk_live_v1_xxx') as client:
message = await client.messages.send(to='+1555...', text='Hello!')
API Reference
Sendly / AsyncSendly
Constructor
Sendly(
api_key: str,
base_url: str = 'https://sendly.live/api',
timeout: float = 30.0,
max_retries: int = 3,
)
Properties
messages - Messages resource
base_url - Configured base URL
Methods
is_test_mode() - Returns True if using a test API key
get_rate_limit_info() - Returns current rate limit info
close() - Close the HTTP client
client.messages
send(to, text, from_=None) -> Message
Send an SMS message.
list(limit=None) -> MessageListResponse
List sent messages.
get(id) -> Message
Get a specific message by ID.
Support
License
MIT