- Documentation
- Requirements
- Features
- Installation
- Quick Start
- Authentication
- Usage Guide
- Response Objects
- Error Handling
- Logging
- Running Locally
- Contributing
- Changelog
- License
- Next SMS Registration
- Python 3.8+
- requests >= 2.28
| Feature | Support |
|---|---|
| Send SMS to a single recipient | ✅ |
| Broadcast same message to multiple recipients | ✅ |
| Send different messages to different recipients | ✅ |
| Schedule SMS (one-time) | ✅ |
| Schedule SMS (recurring: hourly / daily / weekly / monthly) | ✅ |
| Flash (Class-0) SMS | ✅ |
| Auto-retry on transient 5xx errors | ✅ |
| Typed responses — no raw dict-wrangling | ✅ |
Full type annotations + py.typed (mypy strict) |
✅ |
| Environment-variable credentials (12-factor ready) | ✅ |
Zero dependencies beyond requests |
✅ |
| Python 3.8 – 3.12 | ✅ |
pip install pynextsmsfrom pynextsms import SMSClient
client = SMSClient(token="your_bearer_token", sender_id="YOURBRAND")
resp = client.sms.send("255763930052", "Hello from PyNextSMS!")
print(resp)
# SMSResponse(✅ sent, http=200, ref='a3f1c2d4')Generate your Bearer Token from your Next SMS API Documentation.
client = SMSClient(token="your_bearer_token", sender_id="YOURBRAND")export PYNEXTSMS_TOKEN="your_bearer_token"
export PYNEXTSMS_SENDER_ID="YOURBRAND"client = SMSClient() # reads from environment automaticallyresp = client.sms.send("255763930052", "Hello, Ronald!")
if resp.successful:
print(f"✅ Sent! message_id={resp.message_id}, ref={resp.reference}")
else:
print(f"❌ Failed: {resp.raw}")Send the same message to many people in a single API call:
resp = client.sms.send(
to=["255763930052", "255627350020", "255622999999"],
text="Hello everyone — you are now registered!",
reference="campaign_june", # optional tracking ref
)
print(f"✅ Broadcast successful: {resp.successful}")from pynextsms import MessageRecipient
resp = client.sms.send_bulk(
messages=[
MessageRecipient(to="255763930052", text="Hello Daniel, welcome!"),
MessageRecipient(to="255627350020", text="Hello Patricia, welcome!"),
MessageRecipient(to="255622999999", text="Hello Precious, welcome!"),
],
reference="onboarding_batch_001",
)
print(f"✅ Sent {resp.total} personalised messages")Each MessageRecipient can also override the sender ID:
MessageRecipient(to="255763930052", text="Hi!", sender_id="CUSTOM")from datetime import date, time
from pynextsms import ScheduleOptions
opts = ScheduleOptions(
send_date=date(2025, 6, 1),
send_time=time(9, 0), # 09:00, 24-hour clock
)
resp = client.sms.schedule(
to="255763930052",
text="Good morning! Your session starts in 1 hour.",
options=opts,
)
print(f"✅ Scheduled: {resp.successful}")from pynextsms import ScheduleOptions, RepeatInterval
opts = ScheduleOptions(
send_date = date(2025, 6, 1),
send_time = time(8, 0),
repeat = RepeatInterval.DAILY,
start_date = date(2025, 6, 1),
end_date = date(2025, 6, 30),
)
resp = client.sms.schedule(
to="255763930052",
text="Daily reminder: drink water 💧",
options=opts,
)Available repeat intervals:
| Value | Constant |
|---|---|
"hourly" |
RepeatInterval.HOURLY |
"daily" |
RepeatInterval.DAILY |
"weekly" |
RepeatInterval.WEEKLY |
"monthly" |
RepeatInterval.MONTHLY |
Pass flash=True to any send or send_bulk call:
resp = client.sms.send("255712345678", "Urgent alert!", flash=True)The client implements __enter__ / __exit__ so it can be used as a context
manager — the HTTP connection pool is automatically released on exit:
with SMSClient(token="...", sender_id="BRAND") as client:
client.sms.send("255712345678", "Hello!")
# connection pool closed here| Variable | Description |
|---|---|
PYNEXTSMS_TOKEN |
Bearer token (required if not passed to constructor) |
PYNEXTSMS_SENDER_ID |
Default sender ID |
# .env file (use python-dotenv or similar)
PYNEXTSMS_TOKEN=your_bearer_token
PYNEXTSMS_SENDER_ID=YOURBRANDfrom dotenv import load_dotenv
load_dotenv()
from pynextsms import SMSClient
client = SMSClient()Returned by sms.send() and sms.schedule().
| Attribute | Type | Description |
|---|---|---|
successful |
bool |
True when HTTP 2xx |
status_code |
int |
Raw HTTP status |
message_id |
str | None |
ID assigned by API |
reference |
str | None |
Tracking reference |
raw |
dict |
Full JSON response body |
resp = client.sms.send("255763930052", "Hello!")
print(resp.successful) # True
print(resp.message_id) # "msg_abc123"
print(resp.to_dict()) # plain dict
print(resp.to_json()) # JSON stringReturned by sms.send_bulk(). Same as SMSResponse plus:
| Attribute | Type | Description |
|---|---|---|
total |
int |
Number of messages in the batch |
resp = client.sms.send_bulk([...])
print(f"Accepted {resp.total} messages")All exceptions inherit from PyNextSMSError:
from pynextsms import (
PyNextSMSError,
AuthenticationError,
ValidationError,
RateLimitError,
APIError,
)
try:
resp = client.sms.send("255712345678", "Hello!")
except AuthenticationError:
# Bad or missing bearer token
print("Check your PYNEXTSMS_TOKEN.")
except ValidationError as e:
# Bad input caught *before* the HTTP call
print(f"Input error: {e}")
except RateLimitError as e:
# HTTP 429
import time
print(f"Rate limited — retrying in {e.retry_after}s")
time.sleep(e.retry_after or 5)
except APIError as e:
# Any other non-2xx response
print(f"API error (HTTP {e.status_code}): {e}")
except PyNextSMSError as e:
# Catch-all for any other SDK error
print(f"SDK error: {e}")PyNextSMS uses Python's standard logging under the pynextsms logger.
import logging
# Show all SDK log messages (DEBUG and above)
logging.basicConfig(level=logging.DEBUG)
# Or configure just the pynextsms logger
logger = logging.getLogger("pynextsms")
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s"))
logger.addHandler(handler)git clone https://github.com/ronaldgosso/pynextsms.git
cd pynextsmspython -m venv .venv
source .venv/bin/activate # Linux / macOS
# .venv\Scripts\activate # Windowspip install -e ".[dev]"export PYNEXTSMS_TOKEN="your_bearer_token"
export PYNEXTSMS_SENDER_ID="YOURBRAND"python - << 'EOF'
from pynextsms import SMSClient
with SMSClient() as client:
resp = client.sms.send("255763930052", "Hello from local dev!")
print(resp)
EOF# All tests, verbose
pytest
# With coverage report
pytest --cov=pynextsms --cov-report=term-missing
# Specific test class
pytest tests/ -v -k "TestSend"ruff check pynextsms/ # linter
black --check pynextsms/ # formatter check
mypy pynextsms/ # strict type checkingContributions are welcome — bug reports, feature requests, documentation improvements, and pull requests all appreciated.
- Fork the repository on GitHub.
- Clone your fork:
git clone https://github.com/YOUR_USERNAME/pynextsms.git cd pynextsms - Create a branch for your change:
git checkout -b feat/my-new-feature # or git checkout -b fix/the-bug-description - Install dev dependencies:
python -m venv .venv && source .venv/bin/activate pip install -e ".[dev]"
- Make your changes, then make sure all of these pass:
pytest # all tests pass ruff check pynextsms/ # no lint errors black pynextsms/ tests/ # code is formatted mypy pynextsms/ # no type errors
- Commit with a clear message:
git commit -m "feat: add support for sending MMS" - Push and open a Pull Request against
main.
| Prefix | When to use |
|---|---|
feat: |
New feature |
fix: |
Bug fix |
docs: |
Documentation change |
test: |
Adding or updating tests |
refactor: |
Code change with no behaviour change |
chore: |
Tooling, CI, config |
Open an issue at github.com/ronaldgosso/pynextsms/issues and include:
- Python version (
python --version) - pynextsms version (
pip show pynextsms) - Minimal code that reproduces the issue
- Full traceback
- Initial release
sms.send()— single and broadcast sendssms.send_bulk()— personalised bulk sendssms.schedule()— one-time and recurring scheduled SMS- Flash SMS support
- Automatic retries on 5xx errors
- Full type annotations,
py.typedmarker - Comprehensive test suite (95% coverage)
MIT © Ronald Gosso — see LICENSE.