Metadata-Version: 2.5
Name: nextyug-relay
Version: 0.1.1
Summary: Client for Nextyug Relay. Send SMS and email, and read the delivery log.
Project-URL: Homepage, https://relay.nextyug.ai
Project-URL: Source, https://dev.azure.com/nextyug/nextyug/_git/nextyug-package
License: MIT License
        
        Copyright (c) 2026 Nextyug Technologies Private Limited
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: delivery,email,msg91,nextyug,otp,relay,sms
Classifier: Development Status :: 3 - Alpha
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: Topic :: Communications
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: httpx>=0.28
Description-Content-Type: text/markdown

# nextyug-relay

Client for [Nextyug Relay](https://dev.azure.com/nextyug/nextyug/_git/nextyug-relay).
Send SMS and email, and read the delivery log.

## Install

```sh
pip install nextyug-relay
```

Public on PyPI. No index configuration, no token.

<details>
<summary>Pre-release builds from the private feed</summary>

Release candidates go to the `nextyug` feed on Azure Artifacts first. To take
one, name the feed as an explicit source in your `pyproject.toml`:

```toml
[[tool.uv.index]]
name = "nextyug"
url = "https://pkgs.dev.azure.com/nextyug/_packaging/nextyug/pypi/simple/"
explicit = true

[tool.uv.sources]
nextyug-relay = { index = "nextyug" }
```

`explicit = true` means uv looks in that index only for packages pinned to it.
Without it the feed joins the general search path, and a public package of the
same name could be resolved instead.

Authentication is a personal access token with Packaging (Read) scope, in
`~/.netrc` or as `UV_INDEX_NEXTYUG_PASSWORD`. In Azure Pipelines the
`PipAuthenticate@1` task supplies the build identity's own token instead.

</details>

## Use

```python
from nextyug_relay import Relay, RelayError

relay = Relay(api_key=os.environ["RELAY_API_KEY"])

message = await relay.send(
    channel="sms",
    to="+919876543210",
    template="otp_login",
    variables={"code": "482913"},
    idempotency_key=f"otp-{request_id}",
)
print(message.id, message.status, message.segments, message.cost_credits)
```

`idempotency_key` is required, not optional. It is what makes a retry after a
network timeout safe: the same key returns the original message rather than
sending a second time. Use something stable from your own domain — a request
id, an invoice id, a bill id — never a fresh UUID per attempt.

`message.deduplicated` is `True` when relay returned an existing message
instead of queueing a new one.

### Reading the log

```python
message = await relay.get(message_id)
for event in message.events:
    print(event["occurred_at"], event["status"], event["source"])

failures = await relay.list(status="failed", channel="sms", limit=100)
```

### Lifetime

`Relay` owns an `httpx.AsyncClient` unless you pass one in. In a FastAPI app,
build it once at startup rather than per request:

```python
relay = Relay(api_key=settings.relay_api_key)

@asynccontextmanager
async def lifespan(app):
    yield
    await relay.aclose()
```

For a script, use it as a context manager:

```python
async with Relay(api_key=key) as relay:
    await relay.send(...)
```

### Errors

Every failure raises `RelayError` carrying `status_code`, `code` and `detail`.

| Situation | `status_code` | `code` |
|---|---|---|
| Unknown or revoked API key | 401 | |
| Key missing the `messages:send` scope | 403 | |
| Wallet cannot cover the send | 402 | `insufficient_credits` |
| No template by that key | 404 | `template_unknown` |
| No provider configured for the channel | 409 | `provider_missing` |
| Address or template variables do not check out | 422 | `address_invalid`, `variables_missing` |
| Relay unreachable | 0 | `unreachable` |

`error.retryable` is `True` for transport failures, 429 and 5xx. Everything
else is a decision you have to make differently, not the same call again.

A suppressed address is **not** an error. It comes back as a message with
`status == "suppressed"` and `cost_credits == 0`, because it is a delivery
outcome worth logging, not a failed call.

## Requirements

Python 3.11 or newer, and `httpx`. Nothing else.
