Metadata-Version: 2.5
Name: rodmena-caas-client
Version: 0.1.0
Summary: Python client for the Rodmena CaaS platform: SMS, voice, WhatsApp, Telegram, OTP and voicemail
Project-URL: Homepage, https://tel.rodmena.co.uk
Project-URL: Documentation, https://tel.rodmena.co.uk/llms.txt
Project-URL: Repository, https://github.com/rodmena-limited/cass-client
Project-URL: Issues, https://github.com/rodmena-limited/cass-client/issues
Author-email: RODMENA LIMITED <hello@rodmena.co.uk>
License: MIT
License-File: LICENSE
Keywords: caas,otp,rodmena,sms,telephony,voice
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Communications :: Telephony
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: httpx>=0.24
Requires-Dist: pydantic>=2.5
Description-Content-Type: text/markdown

# rodmena-caas-client

Python client for the Rodmena CaaS platform — SMS, voice calls, WhatsApp,
Telegram, OTP and voicemail, from one API key.

```bash
pip install rodmena-caas-client
```

## Quick start

```python
from caas_client import Client

rcs = Client("tel_your_api_key")  # or set CAAS_API_KEY

rcs.sms(to="+447700900555", message="Your order has shipped")
rcs.sms(to="+447700900555", message="Reminder", delay=3600)  # in an hour
rcs.sms(to="+447700900555", message="Tomorrow", at="2026-09-13T09:00:00+01:00")

rcs.call(to="+447700900555", dialog="Your code is 4 2 4 2", repeat=2)

code = rcs.otp.send(to="+447700900555")  # sms, or channel="voice"
rcs.otp.verify(to="+447700900555", code="123456").verified

for vm in rcs.voicemail(unread=True):
    print(vm.from_, vm.duration_seconds)
    open(f"{vm.id}.mp3", "wb").write(rcs.voicemails.audio(vm.id))

rcs.status()  # what this key is allowed to do
rcs.usage()  # cost so far and quota remaining
```

Everything is available asynchronously with the same surface:

```python
from caas_client import AsyncClient

async with AsyncClient() as rcs:
    await rcs.sms(to="+447700900555", message="hello")
```

## Scheduling

`sms()`, `whatsapp()`, `telegram()` and `call()` take `at` (a timezone-aware
`datetime` or ISO-8601 string) or `delay` (seconds). Passing either defers the
send: nothing is transmitted and nothing is charged until it fires.

```python
job = rcs.sms(to="+447700900555", message="later", delay=600)
job.scheduled  # True
job.id  # "sch_..."

rcs.scheduled.list()  # everything queued
rcs.scheduled.cancel(job.id)
```

A cancelled send never transmits. Once it has fired, cancelling raises
`AlreadyFired`.

## Channels

| Channel | Ready to use |
|---|---|
| `sms` | yes |
| `call` | yes |
| `whatsapp` | needs a WhatsApp Business account on your number; until then `ChannelNotProvisioned` |
| `telegram` | the recipient must open your bot's link once; until then `RecipientNotLinked`, whose message carries the link |

Telegram cannot address a phone number on its own — a bot may only reply to a
chat the recipient opened. The first send to an unknown recipient fails with
`RecipientNotLinked`; give the person the `t.me/...` link in the error, and
subsequent sends to that number work like any other.

## Errors

Every non-2xx raises a subclass of `CaaSError` carrying `.status`, `.message`
and `.body`:

`AuthError` (401), `PermissionDenied` (403), `NotFound` (404),
`InvalidRequest` (400/422), `Conflict` (409) and its subclasses
`ChannelNotProvisioned`, `RecipientNotLinked`, `AlreadyFired`,
`QuotaExceeded` (429, with `.retry_after`), `ServiceUnavailable` (503),
`ServerError` (5xx), `TransportError` (no response).

`at`/`delay` are validated before the request, so a bad schedule raises
`InvalidRequest` without spending a round trip.

## Key management

```python
rcs.status().active
new_key = rcs.api.rotate()  # the client keeps working with the new key
```

Rotation returns the only copy of the new key and the previous key stops
working immediately — store it before doing anything else.

## Configuration

| Argument | Environment | Default |
|---|---|---|
| `api_key` | `CAAS_API_KEY` | — |
| `base_url` | `CAAS_URL` | `https://tel.rodmena.co.uk` |
| `timeout` | — | 30 s |

Full API reference: <https://tel.rodmena.co.uk/llms.txt>
