Reusable SendGrid email client for Altissimo Python projects.
- 📧 Plain text emails via
send_text() - 🎨 HTML emails via
send_html() - 📋 Dynamic templates via
send_template() - 🏗️ Factory method —
SendGridClient.from_env()readsSENDGRID_API_KEYfrom the environment - 🔌 Optional dependency —
sendgridSDK is lazily imported with a helpful error message - 📐 Typed — full type annotations with
py.typedPEP 561 marker - ✅ Consistent return type — all methods return a
SendResultdataclass - 🔄 Retry with backoff — configurable retry for transient failures
- 🔔 Fails loudly — send failures raise
SendGridSendErrorby default; opt out per client for batch sends
- Python 3.11+
Published on PyPI as altissimo-sendgrid;
the import name is altissimo.sendgrid.
pip install "altissimo-sendgrid[sendgrid]" # core + SendGrid SDKfrom altissimo.sendgrid import SendGridClient
client = SendGridClient.from_env()
# Plain text
result = client.send_text(
to="user@example.com",
subject="Hello",
body="Welcome aboard!",
)
# HTML
result = client.send_html(
to="user@example.com",
subject="Hello",
html="<h1>Welcome!</h1>",
reply_to="support@example.com",
)
# Template
result = client.send_template(
to="user@example.com",
template_id="d-abc123",
dynamic_data={"first_name": "Alice", "year": 2026},
)
print(result.status_code)By default a failed send raises SendGridSendError. The originating exception
is preserved as __cause__, and the full SendResult is attached as .result:
from altissimo.sendgrid import SendGridClient, SendGridSendError
try:
result = client.send_html(to="user@example.com", subject="Hi", html="<p>Hi</p>")
except SendGridSendError as exc:
logger.error("Welcome email failed (status=%s): %s", exc.status_code, exc)
else:
logger.info("Welcome email sent: status=%s", result.status_code)For batch sends where one bad recipient should not abort the run, opt out and
check result.ok yourself:
client = SendGridClient.from_env(raise_on_error=False)
for user in users:
result = client.send_text(to=user.email, subject="Hi", body="Hello")
if not result.ok:
failures.append((user.email, result.status_code, result.error))SendResult.raise_for_status() converts a result to an exception on demand
(mirroring requests.Response.raise_for_status), which is handy when you want
swallow semantics in one place and raise semantics in another:
client.send_html(to=..., subject=..., html=...).raise_for_status()Note: the library does not log send failures above
DEBUGwhenraise_on_error=True— it has no recipient or subject/template context worth logging from that frame, and a log there would make the failure look handled. Reporting is the caller's job.
| Method | Description |
|---|---|
SendGridClient(api_key, default_from?, max_retries?, retry_delay?, sandbox_mode?, raise_on_error?) |
Create a client with an explicit API key |
SendGridClient.from_env(env_var?, default_from?, max_retries?, retry_delay?, sandbox_mode?, raise_on_error?) |
Create a client from an environment variable |
send_text(to, subject, body, from_email?, reply_to?, cc?, bcc?, sandbox?) |
Send a plain-text email |
send_html(to, subject, html, from_email?, reply_to?, cc?, bcc?, sandbox?) |
Send an HTML email |
send_template(to, template_id, dynamic_data?, from_email?, reply_to?, cc?, bcc?, sandbox?) |
Send a dynamic template email |
| Field | Type | Description |
|---|---|---|
ok |
bool |
Whether the request succeeded (2xx status) |
status_code |
int |
HTTP status code from SendGrid |
body |
str |
Response body |
headers |
dict[str, str] |
Response headers |
error |
str | None |
Error message on failure |
exception |
Exception | None |
Originating exception on failure |
| Method | Description |
|---|---|
raise_for_status(context?) |
Raise SendGridSendError if ok is False; no-op otherwise |
| Exception | Raised when |
|---|---|
SendGridError |
Base class for all library errors |
SendGridImportError |
The sendgrid SDK is not installed |
SendGridSendError |
A send failed (carries status_code and result) |
altissimo.sendgrid
├── __init__.py # Public API surface
├── client.py # SendGridClient with lazy SDK initialization
├── exceptions.py # SendGridError, SendGridImportError, SendGridSendError
├── models.py # SendResult and EmailAddress dataclasses
└── py.typed # PEP 561 marker
# Install all dependencies
poetry sync
# Run tests
poetry run pytest
# Run tests with coverage
poetry run pytest --cov=altissimo --cov-report=term-missing
# Run linters
poetry run ruff check .
poetry run ruff format --check .See CONTRIBUTING.md for detailed development guidelines.
See CHANGELOG.md for version history.
For reporting security vulnerabilities, see SECURITY.md.
Apache License 2.0 — see LICENSE for details.