Metadata-Version: 2.1
Name: dakiya
Version: 2.3
Summary: Relay Communcation via Nitro's Communication Hub
Author: Shamail Tayyab
Author-email: tayyab.shamail@gmail.com
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Description-Content-Type: text/markdown
Requires-Dist: requests

# Dakiya Python Client

Relay communication (email, SMS, WhatsApp, Telegram, voice) via Nitro's Communication Hub.

Dakiya is a thin client. Templates, providers, DLT sender IDs and OTP storage all live in the
hub — your application never holds a provider key or an OTP.

```bash
pip install dakiya
```

## Configuration

Read from the environment **at import time**:

| Variable | Required | Default | Notes |
|---|---|---|---|
| `DAKIYA_AUTH` | yes | `local_dev_key` | S2S token, sent as `Authorization: S2S <token>` |
| `DAKIYA_APPNAME` | yes | `GLOBAL` | Sent as the `X-App` header |
| `DAKIYA_HOST` | no | `http://localhost:10400` | **Only honoured when `MODE=DEVELOPMENT`** |
| `MODE` | no | — | Set to `DEVELOPMENT` to use `DAKIYA_HOST` and log every request |

The host is `https://dakiya.nitrocommerce.ai` unless `MODE=DEVELOPMENT`. In-cluster deployments
set both:

```bash
MODE=DEVELOPMENT
DAKIYA_HOST=http://dakiya.nitrox-production.svc.cluster.local:10400
```

> Load your `.env` **before** importing dakiya. `AUTH`, `APP_NAME` and `HOST` are module-level
> globals captured on import, so a `load_dotenv()` that runs afterwards has no effect. If you
> cannot control import order, set them directly: `dakiya.AUTH = ...`.

## Sending

Methods are named `send_<channel>` / `verify_<channel>`, where channel is one of `email`, `sms`,
`whatsapp`, `telegram`, `voice`. The template name must end in `.html`.

### Email

```python
from dakiya import transmitter

result = transmitter.send_email(
    "nitrox/welcome.html",
    to="john.doe@example.com",
    subject="Hello World!",
    attachments=[open("/tmp/image.png", "rb")],
    who="Shamail",
    time="1:10",
)
```

Attachments must be opened in binary mode. Allowed types: `csv`, `txt`, `zip`, `pdf`, `docx`,
`xlsx`, `jpg`, `jpeg`, `png`.

### SMS OTP

The hub generates, stores and checks the code. You never see it.

```python
from dakiya import transmitter

VARS = {                      # must be identical on send and verify
    "domain_name": "Nitro People",
    "country_code": "+91",
    "mobile": "9812345678",
}

# 1. send
transmitter.send_sms("nitrox/otp.html", to="+919812345678", subject="OTP", vars=VARS)

# 2. verify the code the user typed
res = transmitter.verify_sms(to="+919812345678", vars={**VARS, "otp": user_entered_otp})
verified = res.get("message") in ("VERIFIED", "OK")
```

Three things that are easy to get wrong:

1. **The recipient comes from `vars.country_code` + `vars.mobile`.** Passing only `to` fails with
   `Country code and mobile are required`. Pass both.
2. **Send and verify must carry the same `vars`.** That is how the hub keys the pending code.
3. **The code is currently 4 digits and may start with `0`.** Keep it a string — don't
   `int()` it, and don't hard-code a 6-character input in your UI.

Successful send:

```json
{"message": "OK", "code": 2001, "success": true,
 "results": [{"phone": 9812345678, "transaction_id": "1786...", "sms_cost": 1}],
 "request_id": "1e4b6e6f-b16c-4f2d-87da-c2e97d22c415"}
```

Verify returns `{"message": "VERIFIED"}` on success and `{"message": "Not verified", "code": 2023}`
otherwise — both with a 2xx status, so check the body, not the status code.

## Errors

Every failure raises `TransmitterException(code, message)`.

| HTTP | Code | Meaning |
|---|---|---|
| 401 | 4012 | `Unauthenticated` — bad or missing `DAKIYA_AUTH` |
| 400 | 40017 | `No such template` — check the template path |
| 404 | 4048 | `Country code and mobile are required` — missing `vars.country_code` / `vars.mobile` |
| 202 | 2023 | `Not verified` — wrong or expired OTP (not an exception) |
| — | 0 | `Connection Error` / `Downstream said: <status>` |

Templates are **not** scoped to your app: `nitrox/otp.html` is reachable with `X-App: PEOPLE`.
A template named after your own app only works if someone created it.

## Timeouts

`requests.post()` is called **without a timeout**, so an unresponsive hub blocks the calling
thread indefinitely. In a web app or a scheduler that is enough to wedge the process. Until the
client sets one itself, wrap it:

```python
import dakiya, requests

class _Timeout:
    def __init__(self, real, timeout):
        self._real, self._timeout = real, timeout
    def post(self, *a, **kw):
        kw.setdefault("timeout", self._timeout)
        return self._real.post(*a, **kw)
    def __getattr__(self, name):
        return getattr(self._real, name)

dakiya.requests = _Timeout(requests, (5, 15))
```

Note that dakiya only catches `requests.exceptions.ConnectionError`. A **read** timeout is not a
`ConnectionError`, so it propagates as `requests.exceptions.ReadTimeout` — catch both.

## Testing

There is no sandbox or dry-run mode: a successful `send_sms` puts a real message on a real phone
and bills for it (`sms_cost`). Stub `transmitter.send_sms` / `transmitter.verify_sms` in tests.

`MODE=DEVELOPMENT` prints the full URL, payload and headers, including `DAKIYA_AUTH`. Keep it off
in production.

## Build and upload

```
make build
make upload
```
