# Python

The SDK, and the same call with the standard library when a dependency is not worth it.

### Install

Python 3.9 or newer:
```bash
pip install emailssh
```

### Send

send.py:
```py
import os
from emailssh import Emailssh

# The key comes from https://emails.sh/dashboard/api-keys and lives in
# EMAILSSH_API_KEY in your environment or .env file.
mail = Emailssh(api_key=os.environ["EMAILSSH_API_KEY"])

sent = mail.send(
    from_="Acme <hello@acme.com>",
    to=["ada@example.com"],
    subject="Your receipt from Acme",
    html="<p>Thanks for your order.</p>",
    text="Thanks for your order.",
)

print("queued", sent["id"])
```

The argument is from_ with a trailing underscore, because from is a keyword in Python. It goes over the wire as from, and every other field is named exactly as the API names it. to takes a string or a list.

### Handle a refusal

Errors carry a code and a sentence:
```py
import os
from emailssh import Emailssh, EmailsshError

mail = Emailssh(api_key=os.environ["EMAILSSH_API_KEY"])

try:
    sent = mail.send(
        from_="Acme <hello@acme.com>",
        to=["ada@example.com"],
        subject="Your receipt from Acme",
        html="<p>Thanks for your order.</p>",
    )
    print("queued", sent["id"])
except EmailsshError as err:
    # err.code is the machine-readable reason, err.next_step says what to do
    # about it in prose, and err.status is the HTTP status.
    print(err.code, err, err.next_step)
    if err.code == "rate_limited":
        pass  # err.retry_after is seconds. Wait, do not retry harder.
```

### Standard library only

No dependency, no install step, and it runs anywhere Python does. This is the version to paste into a Lambda that already has enough in its bundle.

email.py, no dependencies:
```py
import json
import os
import urllib.error
import urllib.request


def send_email(to: str, subject: str, html: str) -> str:
    body = json.dumps({
        "from": "Acme <hello@acme.com>",
        "to": [to],
        "subject": subject,
        "html": html,
    }).encode()

    request = urllib.request.Request(
        "https://emails.sh/v1/emails",
        data=body,
        headers={
            "Authorization": f"Bearer {os.environ['EMAILSSH_API_KEY']}",
            "Content-Type": "application/json",
        },
        method="POST",
    )

    try:
        with urllib.request.urlopen(request, timeout=10) as response:
            return json.load(response)["id"]
    except urllib.error.HTTPError as err:
        refusal = json.loads(err.read())["error"]
        raise RuntimeError(f"{refusal['code']}: {refusal['message']} {refusal.get('next', '')}") from err


if __name__ == "__main__":
    print(send_email("ada@example.com", "Your receipt", "<p>Thanks.</p>"))
```

### With requests

If requests is already in the project:
```py
import os
import requests

response = requests.post(
    "https://emails.sh/v1/emails",
    headers={"Authorization": f"Bearer {os.environ['EMAILSSH_API_KEY']}"},
    json={
        "from": "Acme <hello@acme.com>",
        "to": ["ada@example.com"],
        "subject": "Your receipt from Acme",
        "html": "<p>Thanks for your order.</p>",
    },
    timeout=10,
)
response.raise_for_status()
print(response.json()["id"])
```

Where the call belongs in a web app: /docs/django, /docs/flask, /docs/fastapi.

---

Base URL: https://emails.sh/v1. Auth: `Authorization: Bearer esh_...`.
Whole API in one file: https://emails.sh/llms.txt. All documentation: https://emails.sh/docs.md.
