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.1.tar.gz (79.8 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.1-py3-none-any.whl (14.1 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: onesms-0.1.1.tar.gz
  • Upload date:
  • Size: 79.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for onesms-0.1.1.tar.gz
Algorithm Hash digest
SHA256 fff192b9b4f01b5323d95534f352ba91078d1cdc8475cd289828a0e45ee8b41d
MD5 04e543aa69ac5a93671d0949f2eddbf6
BLAKE2b-256 a2c662bafdbefb831f57564b9c83e70114c2b47aa8ec056a6370532c790b73fd

See more details on using hashes here.

Provenance

The following attestation bundles were made for onesms-0.1.1.tar.gz:

Publisher: release.yml on martian56/onesms-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

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

File metadata

  • Download URL: onesms-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 14.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for onesms-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 33865989996610e83d064b4d627fe3e200570878d82ced98228de3d5df76ef6b
MD5 0c2c248dd70e51ab3983fdbde5fd8c27
BLAKE2b-256 ac62c43cb94e3e4056f1e80998ad3bdbfc3da20a08f7e7d3378b69cbec29643a

See more details on using hashes here.

Provenance

The following attestation bundles were made for onesms-0.1.1-py3-none-any.whl:

Publisher: release.yml on martian56/onesms-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

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