Metadata-Version: 2.4
Name: wraps-email
Version: 0.1.0
Summary: Send email via AWS SES from Python. Raw html/text, attachments, SES-stored templates, and suppression management. Your AWS account, no vendor lock-in.
Project-URL: Homepage, https://github.com/wraps-team/wraps-py/tree/main/packages/email#readme
Project-URL: Repository, https://github.com/wraps-team/wraps-py
Project-URL: Issues, https://github.com/wraps-team/wraps-py/issues
Author: Wraps
License-Expression: MIT
License-File: LICENSE
Keywords: aws,aws-ses,email,email-api,email-sdk,mailer,send-email,ses,transactional-email,wraps
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Communications :: Email
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: botocore>=1.34
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.7
Description-Content-Type: text/markdown

# wraps-email

Send email via AWS SES from Python — your AWS account, no vendor lock-in.

A thin, typed wrapper over SES: raw html/text, attachments, SES-stored
templates, and suppression management. Built on `httpx` + `botocore` signing (no
`boto3`), so it stays lightweight and a sync **and** async client can share one
transport core.

```bash
pip install wraps-email      # or: uv add wraps-email
```

```python
from wraps.email import WrapsEmail

email = WrapsEmail(region="us-east-1")
result = email.send(
    from_="you@yourdomain.com",
    to="user@example.com",
    subject="Hello from Python",
    html="<h1>It works</h1>",
    text="It works",
)
print(result.message_id)
```

> Import as `wraps.email`; the distribution is `wraps-email`. `from_` (not
> `from`, which is a Python keyword) is the sender.

## Credentials

Resolved via the standard AWS chain — environment variables, shared config, SSO,
OIDC/web-identity, assume-role, and IMDS. Override explicitly when you need to:

```python
WrapsEmail()                                                    # default chain
WrapsEmail(profile="wraps-dogfood")                            # named profile
WrapsEmail(credentials={"access_key_id": "...", "secret_access_key": "..."})
WrapsEmail(role_arn="arn:aws:iam::123456789012:role/MyRole")   # assume-role / OIDC
```

Tip: send from a **domain identity with DKIM** (not a bare verified address) so
mail authenticates at Gmail/Yahoo.

## Sending

### Attachments

Providing `attachments` switches the send to a raw MIME message automatically.
`content` is bytes, or a string decoded per `encoding` (`"utf-8"` or `"base64"`);
`content_type` is guessed from the filename when omitted. Bcc always rides the
SES envelope, never the visible headers.

```python
from wraps.email import Attachment

email.send(
    from_="you@yourdomain.com",
    to="user@example.com",
    bcc="audit@yourdomain.com",
    subject="Your report",
    html="<p>Attached.</p>",
    attachments=[Attachment(filename="report.csv", content="a,b\n1,2\n", content_type="text/csv")],
)
```

### Batch

Send many independent messages concurrently. A failed message never aborts the
batch; a malformed entry raises before anything is sent.

```python
result = email.send_batch(
    [
        {"from_": "you@x.com", "to": "a@y.com", "subject": "Hi", "text": "1"},
        {"from_": "you@x.com", "to": "b@y.com", "subject": "Hi", "text": "2"},
    ],
    max_concurrency=10,
)
print(result.success_count, result.failure_count)
for entry in result.results:          # aligned to input order
    if not entry.success:
        print(entry.index, entry.error_code, entry.error)
```

## Templates

Manage SES-stored templates and let SES render them at send time.

```python
email.templates.create(name="welcome", subject="Hi {{name}}", html="<h1>{{name}}</h1>")
email.templates.get("welcome")
email.templates.list(page_size=20)          # .next_token to paginate
email.templates.update(name="welcome", subject="Hey {{name}}", html="<h1>{{name}}</h1>")
email.templates.delete("welcome")

email.send_template(
    template="welcome",
    from_="you@yourdomain.com",
    to="user@example.com",
    data={"name": "Sam"},
)
```

## Suppression

The account-level SES suppression list (bounces and complaints).

```python
email.suppression.add("bad@example.com", "COMPLAINT")
email.suppression.get("bad@example.com")     # -> SuppressionEntry | None
email.suppression.list(reason="BOUNCE")      # .next_token to paginate
email.suppression.remove("bad@example.com")
```

## Errors

```python
from wraps.email import SESError, ValidationError, CredentialsError

try:
    email.send(from_="you@x.com", to="user@y.com", subject="Hi", html="<p>Hi</p>")
except ValidationError as err:
    ...                 # bad input, caught before any AWS call (err.field)
except CredentialsError:
    ...                 # no AWS credentials resolved
except SESError as err:
    ...                 # err.code, err.request_id, err.retryable, err.status
```

## Typed

Ships a PEP 561 `py.typed` marker; every public method has an explicit typed
signature, so mypy / ty / Pyright check your calls and editors autocomplete them.

## Status

`0.1.0` — email SDK. Inbound (inbox), event history, reply threading, local
template rendering, and an async client are planned. See the repo roadmap.

MIT licensed.
