Modern Python SDK for the Eskiz.uz SMS gateway
Project description
eskiz-sms
Modern Python SDK for the Eskiz.uz SMS gateway. Sync and async clients, full type hints, Pydantic v2 models.
Discussion group: https://t.me/+xFkMROBeFp45ZmQ0
v1.0 is a ground-up rewrite. The import path and public API changed. See Migrating from v0.x below. v0.x is preserved on the
masterbranch.
Installation
Requires Python 3.11+.
pip install eskiz-sms
Optional extras:
pip install "eskiz-sms[dotenv]" # for DotenvTokenStorage
Quickstart
from eskiz import EskizSMS
with EskizSMS(email="you@example.com", password="your-password") as client:
result = client.sms.send(
mobile_phone="998901234567",
message="Hello from Eskiz!",
from_whom="4546",
)
print(result.id, result.status)
Async
import asyncio
from eskiz import AsyncEskizSMS
async def main() -> None:
async with AsyncEskizSMS(email="you@example.com", password="your-password") as client:
result = await client.sms.send(
mobile_phone="998901234567",
message="Hello!",
)
print(result.id, result.status)
asyncio.run(main())
Resources
The client exposes four resource namespaces. Every method has the same signature on the sync and async clients.
client.auth
| Method | Returns | Notes |
|---|---|---|
me() |
User |
Current account info. |
client.sms
| Method | Returns |
|---|---|
send(...) |
SendResult |
send_global(...) |
SendResult |
send_batch(messages=, ...) |
BatchSendResult |
list_messages(start_date=, ...) |
PaginatedMessages |
list_by_dispatch(dispatch_id=...) |
PaginatedMessages |
dispatch_status(dispatch_id=, user_id=) |
list[DispatchStatusRow] |
status(sms_id) |
SmsStatusDetail |
nicks() |
list[str] |
normalize(message) |
list[NormalizerCharacter] |
check(message) |
SmsCheckResult |
client.templates
| Method | Returns |
|---|---|
create(template) |
TemplateCreated |
list_all() |
TemplateList |
client.reports
| Method | Returns |
|---|---|
balance() |
LimitInfo |
prices() |
PriceList |
totals(year=, month=, is_global=) |
list[Total] |
by_month(year) |
list[TotalByMonth] |
by_smsc(year=, month=) |
list[SmscTotal] |
by_range(start_date=, to_date=, ...) |
list[RangeExpense] |
by_dispatch(dispatch_id=, ...) |
list[DispatchExpense] |
export(year=, month=, ...) |
str (CSV) |
logs(sms_id) |
SmsLogResponse |
Sending a batch
from eskiz import EskizSMS
from eskiz.models import BatchMessage
with EskizSMS(email=..., password=...) as client:
result = client.sms.send_batch(
dispatch_id=42,
messages=[
BatchMessage(user_sms_id="s1", to="998990000001", text="Hi A"),
BatchMessage(user_sms_id="s2", to="998990000002", text="Hi B"),
],
)
Plain dicts work too — to is normalized (+, spaces, dashes stripped) and
ints are coerced:
client.sms.send_batch(
dispatch_id=42,
messages=[{"user_sms_id": "s1", "to": 998990000001, "text": "Hi"}],
)
Token storage
Tokens are cached in memory by default. To persist across runs, pass a
TokenStorage to the client:
from eskiz import DotenvTokenStorage, EskizSMS
with EskizSMS(
email="you@example.com",
password="your-password",
token_storage=DotenvTokenStorage(env_path=".env"),
) as client:
client.sms.send(mobile_phone="998901234567", message="Hi")
# Token written to .env; next run reuses it without re-logging in.
DotenvTokenStorage requires pip install "eskiz-sms[dotenv]". You can also
implement your own storage (Redis, DB, etc.) by conforming to the
TokenStorage Protocol:
class TokenStorage(Protocol):
def get(self) -> str | None: ...
def set(self, token: str) -> None: ...
def clear(self) -> None: ...
Token refresh
The SDK automatically refreshes expired tokens via PATCH /auth/refresh. If
refresh fails (revoked token, etc.), it falls back to a fresh
POST /auth/login. Concurrent callers share a single in-flight refresh —
under load you'll never hammer /auth/login even if 100 requests hit a 401
simultaneously.
Default callback URL
Pass callback_url= to the client and every send uses it unless overridden
per-call:
client = EskizSMS(
email="you@example.com",
password="your-password",
callback_url="https://your-app.com/eskiz/callback",
)
Errors
All errors derive from EskizError:
EskizError
├── EskizHTTPError network / TLS / transport failure
├── AuthError
│ ├── InvalidCredentials login email or password is wrong
│ ├── TokenExpired (rarely surfaced; auto-handled)
│ └── TokenInvalid token revoked or refresh failed
├── EskizBadRequest API rejected the request (validation, etc.)
└── EskizValidationError local input failed validation (bad URL, ...)
The Eskiz* prefix on the exception names is deliberate — BadRequest,
HTTPError, and ValidationError collide with web frameworks, urllib,
and pydantic respectively.
from eskiz import EskizBadRequest, EskizSMS, InvalidCredentials
try:
client.sms.send(mobile_phone="998901234567", message="Hi")
except InvalidCredentials:
print("check your email/password")
except EskizBadRequest as e:
print(f"API error: {e.message} (status={e.status_code})")
Migrating from v0.x
| v0.x | v1.0 |
|---|---|
from eskiz_sms import EskizSMS |
from eskiz import EskizSMS |
EskizSMS(email, password, save_token=True) |
EskizSMS(email=, password=, token_storage=DotenvTokenStorage()) |
eskiz.send_sms(...) |
client.sms.send(...) |
eskiz.send_global_sms(...) |
client.sms.send_global(...) |
eskiz.send_batch(...) |
client.sms.send_batch(...) |
eskiz.get_user_messages(...) |
client.sms.list_messages(...) |
eskiz.get_dispatch_status(...) |
client.sms.dispatch_status(...) |
eskiz.get_template(...) / get_templates() |
client.templates.list_all() |
eskiz.create_template(...) |
client.templates.create(...) |
eskiz.totals(...) |
client.reports.totals(...) |
eskiz.total_by_month(...) |
client.reports.by_month(...) |
eskiz.total_by_smsc(...) |
client.reports.by_smsc(...) |
eskiz.message_export(...) |
client.reports.export(...) |
eskiz.get_limit() |
client.reports.balance() |
eskiz.logs_sms(...) |
client.reports.logs(...) |
eskiz.user |
client.auth.me() |
from eskiz_sms.async_ import EskizSMS |
from eskiz import AsyncEskizSMS |
Other notable changes:
- Python 3.11+ required (was 3.8+).
- All endpoint methods take keyword-only arguments.
- All return values are typed Pydantic models, not raw
dicts. - Contact endpoints (
add_contact,get_contact, etc.) are no longer exposed — they were not in the official Postman collection. client.token.set(...)removed; use a customTokenStorageinstead.
Integration tests
The unit suite uses respx to mock the HTTP layer. A separate, opt-in
integration suite under tests/integration/ exercises the SDK against the
real Eskiz API. It is skipped by default — pass --run-integration to run it.
Set credentials in .env.integration (see .env.integration.example):
cp .env.integration.example .env.integration
# fill in ESKIZ_EMAIL / ESKIZ_PASSWORD
Run only the read-only smokes (no SMS sent, no credits used):
pytest --run-integration tests/integration/test_readonly.py tests/integration/test_async.py
Run the live send test as well — set ESKIZ_TEST_PHONE to a number you
control. Eskiz moderates SMS bodies per-account; if the default Eskiz Test
body isn't approved on yours, set ESKIZ_TEST_BODY to one that is:
ESKIZ_TEST_PHONE=998901234567 pytest --run-integration tests/integration
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file eskiz_sms-1.0.1.tar.gz.
File metadata
- Download URL: eskiz_sms-1.0.1.tar.gz
- Upload date:
- Size: 26.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e9f673a873dd218a11a739dcada2204a59c31b07db6fa096e3c9ccd7ff1d2c7d
|
|
| MD5 |
b5a4a3a87d222044e1ccda5d480066fc
|
|
| BLAKE2b-256 |
af13fa6083bf5ddf2280490827b28d60c032336fd3829f2d5f0f2a55dd6dc601
|
File details
Details for the file eskiz_sms-1.0.1-py3-none-any.whl.
File metadata
- Download URL: eskiz_sms-1.0.1-py3-none-any.whl
- Upload date:
- Size: 38.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cc8787b1ec93a8cec5ef821119ee6387944280025a7601128e398a96fd8b1480
|
|
| MD5 |
46f8db8e426c37bc1722b39a6e6e5fd0
|
|
| BLAKE2b-256 |
f720a0d8b51fa7e6ba32826fc016bf312f337a1d3c01d8c6ccf23a18347e1ff6
|