---
title: "Install"
description: "One package, three dependencies, Python 3.10 and up."
url: "https://openemail.uk/docs/python/install"
area: "Python"
category: "Getting started"
---

# Install

One package, three dependencies, Python 3.10 and up.

## Install

> The current release is `0.0.1`. It has a method for every method of the TypeScript SDK, under the same name in snake_case, and the package’s parity check fails the build when one goes missing or sends a different request.

**Install**

_pip_

```bash
pip install openemail
```

_uv_

```bash
uv add openemail
```

_poetry_

```bash
poetry add openemail
```

**send_email.py**

```
import os

from openemail import init, openemail

init(os.environ['OPENEMAIL_API_KEY'])

email = openemail.emails.send({
    'from': 'Acme Billing <billing@acme.com>',
    'to': 'ada@example.com',
    'subject': 'Your September invoice',
    'html': '<p>Invoice attached.</p>',
})

print(email['id'], email['status'])
```

`init` configures the shared `openemail` client once, and every module that imports `openemail` afterwards gets that client. Skip it and the first call builds the client from `OPENEMAIL_API_KEY` instead.

A request body is a plain `dict` with the API’s own field names, so `from`, `replyTo` and `scheduledAt` read exactly as they do in the API reference, and a response is a `dict` too. Every body and response has a `TypedDict` in `openemail.types`, so your editor completes the keys and a type checker catches a misspelt one.

> `send()` returning does not mean the message has gone. A scheduled or cancellable send comes back `queued` or `scheduled` and settles later. Read `status`, not the fact that the call returned.

## Where it runs

Python 3.10 and newer, with three dependencies: `httpx` for HTTP, `anyio` for the async client and `typing-extensions` for the types. Every request goes through `httpx`, and you can hand it your own `httpx.Client`, or an `httpx.AsyncClient` for `AsyncOpenEmail`.

> `OpenEmail` suits scripts, workers and synchronous web apps, and `AsyncOpenEmail` suits an event loop, on asyncio or trio. Both ship in this one package with the same methods.

> One client is safe to share between threads, so a worker process needs only one. It keeps one connection pool, which `close()` or the end of a `with` block closes.

> The package ships `py.typed` and passes mypy and pyright in strict mode, so a type checker reads every method, body and response as written.

> The client carries a workspace API key that can send mail and read the mailbox, so it belongs on a server, in a job or in a tool that runs on your own machine. Keep the key in an environment variable or a secret manager, never in code.
