Metadata-Version: 2.4
Name: senditdaddy
Version: 0.1.0
Summary: Send email from your SendItDaddy domains.
Project-URL: Homepage, https://senditdaddy.com
Project-URL: Source, https://github.com/CodeCraftStudios/senditdaddy-python-sdk
Project-URL: Issues, https://github.com/CodeCraftStudios/senditdaddy-python-sdk/issues
Author-email: CodeCraft Studios <hello@senditdaddy.com>
License: MIT
License-File: LICENSE
Keywords: email,senditdaddy,smtp,transactional
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Communications :: Email
Classifier: Typing :: Typed
Requires-Python: >=3.9
Provides-Extra: dev
Description-Content-Type: text/markdown

# senditdaddy

Send email from a domain your SendItDaddy workspace has already connected.

No dependencies — the standard library does the HTTP.

```bash
pip install senditdaddy
```

## Send something

```python
from senditdaddy import SendItDaddy

client = SendItDaddy(api_key="sid_sk_...")

email = client.emails.send(
    from_="Acme <hello@yourdomain.com>",
    to="someone@example.com",
    subject="Your receipt",
    html="<p>Thanks for your order.</p>",
)

print(email["id"])   # msg__k3nR8x...
```

`from_` has the trailing underscore because `from` is a Python keyword.
`sender=` works too, and so does a plain dict, which is what makes a payload
copied out of the HTTP docs run unchanged:

```python
client.emails.send({
    "from": "hello@yourdomain.com",
    "to": ["a@example.com", "b@example.com"],
    "cc": "boss@example.com",
    "bcc": [],
    "reply_to": "support@yourdomain.com",
    "subject": "Hello",
    "html": "<p>Hi there</p>",
    "text": "Hi there",
})
```

`to`, `cc` and `bcc` each take one address or a list. Supply `html`, `text`, or
both — with only `html`, a plain-text alternative is generated for you, because
a message with no `text/plain` part scores worse with spam filters.

Get the key from **Dashboard → API keys**. It is shown once, at creation; there
is no endpoint that reveals it again, because it is stored as a hash. Prefer
the environment over a literal:

```python
client = SendItDaddy()          # reads SENDITDADDY_API_KEY
```

## Which addresses can I send from?

Whatever the workspace has connected and verified. Ask, rather than guessing —
an unconnected From address is a 403 that is otherwise hard to diagnose:

```python
for address in client.addresses()["addresses"]:
    print(address["address"], address["can_send"], address["quota"]["remaining"])
```

Each address includes 500 sends per UTC day.

## Read back what you sent

```python
client.emails.get("msg__k3nR8x...")             # one, with bodies
client.emails.list(direction="outbound")        # a page, without bodies
client.emails.list(status="failed", page=2)
```

List responses are `{"results": [...], "count": N, "page": 1, "pages": 3,
"has_next": bool, "has_previous": bool}`. Bodies are left out of the list
deliberately: they are encrypted at rest, and decrypting a page of them for
content the list does not show is work for nothing.

## When it goes wrong

Every failure is a subclass of `APIError`, and `error.code` is the stable thing
to branch on — it does not change when someone improves the wording.

```python
from senditdaddy import (
    AuthenticationError,   # 401  key missing, revoked, or expired
    PermissionDeniedError, # 403  not an address this key may send as
    NotFoundError,         # 404  no such email
    ConflictError,         # 409  the domain has not verified yet
    ValidationError,       # 422  something in the request was not acceptable
    RateLimitError,        # 429  too many requests, or out of daily allowance
    ServerError,           # 5xx  including 502 when the upstream rejected it
    TransportError,        # never got an answer at all
)

try:
    client.emails.send(from_="hello@yourdomain.com", to="a@example.com",
                       subject="Hi", text="Hi")
except ValidationError as error:
    print(error.errors)          # {"text": ["Give the message a body."]}
except RateLimitError as error:
    print(error.code)            # rate_limit_exceeded | daily_send_limit_reached
    print(error.retry_after)     # seconds, when the API gave one
except PermissionDeniedError as error:
    print(error.message)         # "This API key is not allowed to send as ..."
```

**Nothing is retried automatically.** A send is not idempotent: a request that
timed out may well have gone out, and a library that quietly repeats it
delivers the message twice to somebody who only wanted it once. `TransportError`
is deliberately *not* an `APIError` for that reason — it means the outcome is
unknown, which is a different decision from a clean rejection.

## Configuration

| Argument | Default | |
|---|---|---|
| `api_key` | `$SENDITDADDY_API_KEY` | required |
| `base_url` | `$SENDITDADDY_BASE_URL`, else `https://api.senditdaddy.com` | point at your own deployment |
| `timeout` | `30.0` | seconds |
| `transport` | `UrllibTransport` | see below |
| `user_agent` | `senditdaddy-python/<version>` | |

## Bringing your own HTTP client

Applications running on `requests` or `httpx` usually have retries, proxies,
pooling and tracing configured on it. Pass an adapter and every call inherits
all of that instead of quietly going around it. It needs one method:

```python
import requests
from senditdaddy import Response, SendItDaddy

class RequestsTransport:
    def request(self, method, url, *, headers, body=None, timeout=None):
        reply = requests.request(method, url, headers=headers, data=body,
                                 timeout=timeout)
        return Response(reply.status_code, dict(reply.headers), reply.content)

client = SendItDaddy(api_key="sid_sk_...", transport=RequestsTransport())
```

A 4xx must be **returned**, not raised — the body carries the reason, and the
client turns it into the right typed error.

## The HTTP API underneath

```
POST   /api/emails         send             201
GET    /api/emails         list             200   ?direction= ?status= ?from= ?page= ?page_size=
GET    /api/emails/{id}    one, with bodies 200
GET    /api/addresses      what you may send as
```

Authenticate with `Authorization: Bearer sid_sk_...` on every request.

```bash
curl https://api.senditdaddy.com/api/emails \
  -H "Authorization: Bearer $SENDITDADDY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"from":"hello@yourdomain.com","to":"someone@example.com",
       "subject":"Hello","html":"<p>Hi there</p>"}'
```

## Tests

No test dependencies either — the client is exercised against a transport that
records calls and returns canned responses.

```bash
cd sdk/python
python -m unittest discover -s tests
```
