Skip to main content

eskiz-sms

Modern Python SDK for the Eskiz.uz SMS gateway. Sync and async clients, full type hints, Pydantic v2 models.

Downloads Downloads

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 master branch.

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 custom TokenStorage instead.

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)

Source distribution for eskiz-sms 1.0.1
File Size Uploaded
eskiz_sms-1.0.1.tar.gz 26.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for eskiz-sms 1.0.1
File Interpreter ABI Platform
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}

Release history Release notifications | RSS feed

This release

1.0.1 This release

2 release files

0.2.5

2 release files

0.2.4

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.18

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page