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
Metadata
Release files for eskiz-sms 1.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| eskiz_sms-1.0.1.tar.gz | 26.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| eskiz_sms-1.0.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 64.6 kB
Release files / eskiz_sms-1.0.1.tar.gz
| Download URL | eskiz_sms-1.0.1.tar.gz |
|---|---|
| Size | 26.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e9f673a873dd218a11a739dcada2204a59c31b07db6fa096e3c9ccd7ff1d2c7d
|
|
BLAKE2b-256 checksum How to use checksums |
af13fa6083bf5ddf2280490827b28d60c032336fd3829f2d5f0f2a55dd6dc601
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is 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}
|
Release files / eskiz_sms-1.0.1-py3-none-any.whl
| Download URL | eskiz_sms-1.0.1-py3-none-any.whl |
|---|---|
| Size | 38.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cc8787b1ec93a8cec5ef821119ee6387944280025a7601128e398a96fd8b1480
|
|
BLAKE2b-256 checksum How to use checksums |
f720a0d8b51fa7e6ba32826fc016bf312f337a1d3c01d8c6ccf23a18347e1ff6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is 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}
|