Skip to main content

onesms

PyPI CI Docs Python License: MIT

A typed Python client for the 1sms.az SMS API, with both synchronous and asynchronous interfaces.

Documentation: https://martian56.github.io/onesms-sdk/

1sms.az API reference: https://1sms.az/api-docs

Features

  • Sync (Client) and async (AsyncClient) clients with the same surface.
  • OTP, notification, and advertising sends, plus balance and delivery-status lookups.
  • Fully typed responses as frozen dataclasses; ships a py.typed marker.
  • Automatic retries with exponential backoff and jitter, honoring Retry-After.
  • Idempotency-key support so retried sends are never duplicated.
  • A precise exception hierarchy mapped from the API's HTTP status and error codes.
  • Webhook signature verification (HMAC-SHA256) with timestamp tolerance.
  • No hard dependencies beyond httpx.

Requirements

  • Python 3.10 or newer
  • A 1sms.az API key

Installation

pip install onesms

Or add it to a uv project:

uv add onesms

To install the latest unreleased code, point at the repository instead:

pip install git+https://github.com/martian56/onesms-sdk.git

Quickstart

from onesms import Client

with Client("1sk_your_api_key", sender_name="YourSender") as client:
    result = client.send_otp("994501234567", "Your code is 123456")
    print(result.message_id, result.cost, result.balance)

Async

import asyncio

from onesms import AsyncClient


async def main() -> None:
    async with AsyncClient("1sk_your_api_key") as client:
        result = await client.send_otp("994501234567", "Your code is 123456")
        print(result.message_id)


asyncio.run(main())

Usage

Check the balance

balance = client.balance()
print(balance.balance)
print(balance.apis.otp, balance.apis.bulk, balance.apis.advertising)

Send an OTP

result = client.send_otp("994501234567", "Your code is 123456")

Send notifications

A single recipient returns per-message ids; two or more recipients are dispatched as a bulk task and return a task_id you can poll.

single = client.send_notification(["994501234567"], "Order shipped")
print(single.message_ids)

bulk = client.send_notification(
    ["994501234567", "994502223344", "994553334455"],
    "Weekend promotion",
)
print(bulk.task_id, bulk.sent_count, bulk.failed_count)
for item in bulk.rejected:
    print(item.number, item.reason)

Send advertising

result = client.send_advertising(["994501234567"], "Big discounts this week")

Look up delivery status

from onesms import DeliveryStatus

status = client.message_status("message-id")
print(status.status_code, status.status_text)

if status.is_final:
    if status.status is DeliveryStatus.DELIVERED:
        print("delivered")
    else:
        print("not delivered:", status.status_text)

Poll a bulk task

from onesms import Channel

task = client.task_status("task-id", channel=Channel.NOTIFICATION)
print(task.sent_count, task.failed_count)
for message in task.messages:
    print(message.phone, message.status_text, message.is_final)

Idempotency

Pass an idempotency_key to make a send safe to retry. The API remembers the key for 24 hours and will not send the same request twice. When a key is supplied, the client also retries transient network and server errors automatically.

client.send_otp(
    "994501234567",
    "Your code is 123456",
    idempotency_key="order-4821-otp",
)

Retries

GET requests and 429 Too Many Requests responses are always retried. Network failures and 5x responses on writes are retried only when an idempotency key is present, so a send is never silently duplicated. Backoff is exponential with jitter and respects a Retry-After header when the API provides one. Configure the ceiling with max_retries:

client = Client("1sk_your_api_key", max_retries=5, timeout=15.0)

Error handling

Every API failure raises a subclass of OneSmsError.

from onesms import (
    Client,
    InsufficientBalanceError,
    OneSmsAPIError,
    OneSmsConnectionError,
    OneSmsValidationError,
    RateLimitError,
)

try:
    client.send_notification(["994501234567"], "Hello")
except OneSmsValidationError as exc:
    print("invalid input:", exc)
except InsufficientBalanceError as exc:
    print("top up:", exc.required, "have:", exc.balance)
except RateLimitError as exc:
    print("retry after:", exc.retry_after)
except OneSmsConnectionError as exc:
    print("network problem:", exc)
except OneSmsAPIError as exc:
    print(exc.status_code, exc.error_code, exc.message)
Exception Raised for
OneSmsValidationError Client-side checks before a request is sent
OneSmsConnectionError Network failures that could not be retried
BadRequestError 400
AuthenticationError 401
InsufficientBalanceError 402 (exposes required, balance)
PermissionDeniedError 403
NotFoundError 404
ConflictError 409
RateLimitError 429 (exposes retry_after)
ServerError 5xx (exposes msm_errno, msm_err_text, hint)
OneSmsAPIError Base class for any API error

Webhooks

1sms.az signs delivery webhooks with an HMAC-SHA256 signature over the request timestamp and raw body. Verify it with your API secret before trusting the payload.

from onesms import WebhookEvent, verify_signature

signature = request.headers["X-1sms-Signature"]
timestamp = request.headers["X-1sms-Timestamp"]
raw_body = request.get_data()

if not verify_signature("your_api_secret", timestamp, raw_body, signature):
    raise ValueError("invalid signature")

event = WebhookEvent.from_payload(request.get_json())
if event.is_final:
    print(event.message_id, event.status_text)

verify_signature rejects timestamps outside a five-minute window by default. Widen it with tolerance_seconds if needed.

Development

uv sync
uv run ruff check .
uv run ruff format --check .
uv run mypy
uv run pytest

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

onesms-0.1.0.tar.gz (79.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

onesms-0.1.0-py3-none-any.whl (14.0 kB view details)

Uploaded Python 3

File details

Details for the file onesms-0.1.0.tar.gz.

File metadata

  • Download URL: onesms-0.1.0.tar.gz
  • Upload date:
  • Size: 79.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for onesms-0.1.0.tar.gz
Algorithm Hash digest
SHA256 e9b03831a6a762c3928658f3bb1a7e6e0860ec9716f229f4b25585bd215f5e20
MD5 11556cfe211fcc3979fac2c2e440bcea
BLAKE2b-256 7a484af1cb4ea35b55aa3824a5f2015e091210d0b44093e2012b22e2a5ca2c4d

See more details on using hashes here.

File details

Details for the file onesms-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: onesms-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 14.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for onesms-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 237825014d34a5e9cc62058ea42b1a63c1870324815e4b73c6566e72a0330220
MD5 865561816b4f911ee640db85d9c21e2c
BLAKE2b-256 7b21b06283da286ad965a239601dcbf3945731aaeee9b1f83186e0255d2b3d57

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page