smtpkit
smtpkit is a modern, production-ready Python SDK for sending transactional and marketing emails via SMTP (including SendGrid, Amazon SES, Mailgun, Postmark, Gmail, Mailpit, or custom SMTP relays) with built-in Jinja2 templating, structured logging, secure credential handling, and comprehensive exception management.
Features
- 🚀 Universal SMTP Transport: Reliable SMTP delivery supporting TLS encryption, authentication, and timeouts across any SMTP provider.
- 🎨 Built-in & Custom Jinja2 Templates: Pre-built responsive HTML templates (
welcome,reset_password,verify_account) with support for custom template directories. - 🪵 Enterprise Logging: PEP 282 compliant logging with
NullHandlerby default, granular log levels (DEBUG,INFO,ERROR), and automatic password masking. - 🛡️ Comprehensive Exception Hierarchy: Clear, actionable exceptions for validation, templates, authentication, connection timeouts, and delivery errors.
- ⚙️ Flexible Configuration: Seamlessly configure via
.envfiles, environment variables, or typedEmailConfigobjects. - 📦 PEP 561 Compliant: Fully typed (
py.typed) for autocomplete and static analysis with MyPy and IDEs.
Installation
Install via pip:
pip install smtpkit
Quick Start
1. Environment Configuration
Create a .env file in your project root or set environment variables:
# SMTP Configuration (SendGrid, Mailpit, Amazon SES, or custom SMTP)
EMAIL_SMTP_HOST=smtp.sendgrid.net
EMAIL_SMTP_PORT=587
EMAIL_SMTP_USERNAME=apikey
EMAIL_SMTP_PASSWORD=your-smtp-api-key
# Sender Defaults
EMAIL_FROM=notifications@yourdomain.com
EMAIL_FROM_NAME=Your Company
# Optional Settings
EMAIL_USE_TLS=true
EMAIL_TIMEOUT=30
2. Send Plain Text or HTML Email
from smtpkit import EmailClient
# Automatically loads configuration from .env / environment variables
client = EmailClient()
# Send a simple email
client.send(
to="recipient@example.com",
subject="Welcome to Our Platform",
body="Hello! Thank you for joining us.",
)
Working with Templates
smtpkit includes responsive, production-ready HTML templates out of the box.
Built-in Templates
| Template Name | Description | Available Context Variables |
|---|---|---|
welcome |
Onboarding & welcome message | name, company_name, action_url, year |
reset_password |
Password recovery with secure action button | name, reset_url, company_name, expiry_hours, support_email, year |
verify_account |
Email verification & activation link | name, verification_url, company_name, expiry_hours, support_email, year |
Example: Sending a Template Email
from smtpkit import EmailClient
client = EmailClient()
client.send(
to="user@example.com",
subject="Welcome to FireCompass!",
template="welcome",
data={
"name": "Jane Doe",
"company_name": "FireCompass",
"action_url": "https://app.firecompass.com/dashboard",
"year": 2026,
},
)
Example: Using Custom Templates
You can point EmailClient to your own Jinja2 template directory:
from pathlib import Path
from smtpkit import EmailClient
client = EmailClient(template_directory=Path("./my_custom_templates"))
client.send(
to="customer@example.com",
subject="Your Invoice is Ready",
template="monthly_invoice", # Looks for monthly_invoice.html in ./my_custom_templates
data={
"customer_name": "Acme Corp",
"invoice_number": "INV-2026-001",
"amount_due": "$149.00",
},
)
Programmatic Configuration
Instead of environment variables, you can configure the client directly using EmailConfig:
from smtpkit import EmailClient, EmailConfig
config = EmailConfig(
smtp_host="smtp.sendgrid.net",
smtp_port=587,
smtp_username="apikey",
smtp_password="your-api-key-here",
from_email="no-reply@company.com",
from_name="My Company",
use_tls=True,
timeout=30,
)
client = EmailClient(config=config)
Exception Handling
The SDK provides a clean exception hierarchy inheriting from EmailError:
EmailError (Base)
├── EmailConfigurationError
├── EmailValidationError
├── EmailTemplateError
└── EmailSendError
├── EmailConnectionError
├── EmailAuthenticationError
├── EmailTimeoutError
└── EmailRecipientsRefusedError
Example: Robust Error Handling
import logging
from smtpkit import (
EmailClient,
EmailAuthenticationError,
EmailConnectionError,
EmailRecipientsRefusedError,
EmailTimeoutError,
EmailValidationError,
EmailTemplateError,
EmailSendError,
EmailError,
)
client = EmailClient()
try:
client.send(
to="client@example.com",
subject="Important Update",
template="welcome",
data={"name": "Alice", "company_name": "Acme Inc."},
)
except EmailValidationError as e:
print(f"Invalid email input on field '{e.field}': {e}")
except EmailTemplateError as e:
print(f"Template error ({e.template_name}): {e}")
except EmailAuthenticationError as e:
print(f"SMTP Auth failure for user '{e.username}': {e}")
except EmailConnectionError as e:
print(f"Could not connect to SMTP server ({e.host}:{e.port}): {e}")
except EmailTimeoutError as e:
print(f"Operation timed out after {e.timeout}s: {e}")
except EmailRecipientsRefusedError as e:
print(f"Server refused recipients {e.recipients}: {e}")
except EmailSendError as e:
print(f"Failed to send email: {e}")
except EmailError as e:
print(f"General email SDK error: {e}")
Logging Configuration
smtpkit adheres to Python library best practices (PEP 282). By default, it emits no logs unless your application configures logging.
Example: Enabling Logs in Your Application
import logging
import sys
# Configure root or package logger
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
handlers=[logging.StreamHandler(sys.stdout)],
)
# Enable DEBUG logging specifically for smtpkit
logging.getLogger("smtpkit").setLevel(logging.DEBUG)
Security Guarantee: Passwords and sensitive credentials are automatically masked (e.g.
***) and are never written to log files.
Local Development with Mailpit
You can test email sending locally using Mailpit included via Docker Compose:
- Start Mailpit:
docker compose up -d
- Set
.envto point to localhost:EMAIL_SMTP_HOST=localhost EMAIL_SMTP_PORT=1025 EMAIL_USE_TLS=false
- View sent emails in your browser at
http://localhost:8025.
Running Tests
Install dev dependencies and run pytest:
pip install -e ".[dev]"
pytest tests/ -v
Publishing to PyPI
-
Build distribution archives:
python -m build
-
Verify archives with Twine:
twine check dist/*
-
Upload to TestPyPI (Optional):
twine upload --repository testpypi dist/*
-
Upload to PyPI:
twine upload dist/*
License
Distributed under the MIT License.
Release files for smtpkit 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| smtpkit-0.1.1.tar.gz | 22.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| smtpkit-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 42.0 kB
Release files / smtpkit-0.1.1.tar.gz
| Download URL | smtpkit-0.1.1.tar.gz |
|---|---|
| Size | 22.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5c0f034f21c55ca93ed9bbc619286369f21a5376210fc4b052311e4cbcfe8efb
|
|
BLAKE2b-256 checksum How to use checksums |
064ded82ab48a4a85f8e4ade8383bcf3739126ccd8de63fe9b69b11f2a707140
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.6
|
Release files / smtpkit-0.1.1-py3-none-any.whl
| Download URL | smtpkit-0.1.1-py3-none-any.whl |
|---|---|
| Size | 19.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0f106e957102fbe8b4dfe3eeb9ef35850eec00ba3b5e49b523875cd1f1546706
|
|
BLAKE2b-256 checksum How to use checksums |
23294e919040384c375edb503c719c9ce648c1135c45349926fe6f937ca85fec
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.6
|