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 description
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.
pip install wraps-email # or: uv add wraps-email
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 iswraps-email.from_(notfrom, 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:
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.
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.
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.
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).
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
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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file wraps_email-0.1.0.tar.gz.
File metadata
- Download URL: wraps_email-0.1.0.tar.gz
- Upload date:
- Size: 16.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1d699909a48302a9adb5f750a8c27e5a8cabe446483cc66981faeceeb0d6788d
|
|
| MD5 |
ff347b6b2985e68fa4def34691d74bd6
|
|
| BLAKE2b-256 |
fee2845a2fb152599457ec2c8aba16fa3534b6d3e98cd31ef295962a041b907a
|
File details
Details for the file wraps_email-0.1.0-py3-none-any.whl.
File metadata
- Download URL: wraps_email-0.1.0-py3-none-any.whl
- Upload date:
- Size: 18.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
34ff9fd2f4aa27fb0e231a9089a9659933e905fa56bc537043811c6e8b6071ca
|
|
| MD5 |
e630dfc757ae0e128d7693356f96a9d8
|
|
| BLAKE2b-256 |
5039b0758a2f419e73d6e70535e64d4b5a10db76629371a506f78beb07995584
|