Production-ready Python package for sending HTML emails via SMTP with TLS, SSL, CC/BCC, attachments, and retries.
Project description
smtp_mailer
A production-ready Python library for sending HTML emails over SMTP with a clean, intuitive API. Supports STARTTLS and SSL, CC/BCC, file attachments, exponential-backoff retries, batch sending, and a context-manager interface.
Table of Contents
- Features
- Installation
- Quick Start
- Gmail Example
- Outlook Example
- SSL Example (port 465)
- Inline HTML Content
- CC and BCC
- Attachments Example
- Batch Sending
- Context Manager
- Error Handling
- API Reference
- Configuration Reference
- Security Notes
- Publishing to PyPI
Features
- ✅ HTML email body — via file path or inline string
- ✅ Multiple recipients (To, CC, BCC)
- ✅ File attachments (PDF, PNG, JPG, DOCX, TXT, ZIP, CSV, XLSX)
- ✅ STARTTLS (port 587) and SSL (port 465)
- ✅ Exponential-backoff retries for transient errors
- ✅ Configurable timeout
- ✅ Batch sending with per-recipient error tracking
- ✅ Context-manager interface
- ✅ Structured dict return value with
message_idandtimestamp - ✅ RFC-compliant email validation via
email-validator - ✅ Credentials never logged or exposed in exceptions
- ✅ Full type annotations (passes
mypy --strict) - ✅ >90% test coverage
Installation
pip install smtp_mailer
Requirements: Python 3.11+
Quick Start
from smtp_mailer import send_email
result = send_email(
html_file="welcome.html",
subject="Hello from smtp_mailer!",
sender_email="you@gmail.com",
app_password="xxxx xxxx xxxx xxxx",
receiver_email="friend@example.com",
)
print(result["message_id"]) # RFC 2822 Message-ID
print(result["timestamp"]) # ISO 8601 UTC timestamp
Gmail Example
Gmail requires an App Password when 2-Step Verification is enabled.
from smtp_mailer import send_email
send_email(
html_file="templates/welcome.html",
subject="Welcome to our service",
sender_email="you@gmail.com",
app_password="xxxx xxxx xxxx xxxx", # App Password, not your login password
receiver_email="customer@example.com",
smtp_server="smtp.gmail.com", # default
port=587, # default (STARTTLS)
)
Outlook Example
send_email(
html_file="newsletter.html",
subject="Monthly Newsletter",
sender_email="you@outlook.com",
app_password="your-app-password",
receiver_email="subscriber@example.com",
smtp_server="smtp-mail.outlook.com",
port=587,
)
SSL Example (port 465)
send_email(
html_file="notice.html",
subject="Secure Notice",
sender_email="you@example.com",
app_password="secret",
receiver_email="them@example.com",
smtp_server="mail.example.com",
port=465,
use_ssl=True,
)
Inline HTML Content
Use html_content instead of html_file to pass the email body as a string:
send_email(
html_content="<h1>Hello!</h1><p>This is an inline body.</p>",
subject="Inline HTML",
sender_email="you@gmail.com",
app_password="secret",
receiver_email="them@example.com",
)
Note:
html_fileandhtml_contentare mutually exclusive — providing both raises aValueError.
CC and BCC
send_email(
html_file="report.html",
subject="Q3 Report",
sender_email="analytics@company.com",
app_password="secret",
receiver_email="ceo@company.com",
cc=["cfo@company.com", "cto@company.com"],
bcc=["audit@company.com"],
)
BCC addresses are passed in the SMTP envelope only and never appear in email headers.
Attachments Example
send_email(
html_file="invoice_email.html",
subject="Your Invoice #1042",
sender_email="billing@company.com",
app_password="secret",
receiver_email="client@example.com",
attachments=[
"invoices/invoice_1042.pdf",
"assets/logo.png",
],
)
Allowed extensions: .pdf, .png, .jpg, .jpeg, .docx, .doc,
.txt, .zip, .csv, .xlsx
Max size: 25 MB per file.
Batch Sending
Send the same email to many recipients, tracking individual successes and failures:
from smtp_mailer import send_batch_email
results = send_batch_email(
html_file="promo.html",
subject="Exclusive Offer",
sender_email="marketing@company.com",
app_password="secret",
receiver_emails=[
"alice@example.com",
"bob@example.com",
"carol@example.com",
],
per_message_delay=0.5, # seconds between sends (avoids rate-limiting)
)
for recipient, outcome in results.items():
if isinstance(outcome, dict):
print(f"✓ {recipient} — message_id: {outcome['message_id']}")
else:
print(f"✗ {recipient} — error: {outcome}")
Context Manager
The SMTPMailer class stores credentials once and reuses them across sends:
from smtp_mailer import SMTPMailer
with SMTPMailer(
sender_email="you@gmail.com",
app_password="xxxx xxxx xxxx xxxx",
) as mailer:
# Single send
mailer.send(
html_file="welcome.html",
subject="Welcome!",
receiver_email="alice@example.com",
)
# Batch send
results = mailer.send_batch(
html_content="<p>Your weekly digest</p>",
subject="Weekly Digest",
receiver_emails=["bob@example.com", "carol@example.com"],
)
Error Handling
All package exceptions inherit from SMTPMailerError:
from smtp_mailer import send_email
from smtp_mailer.exceptions import (
SMTPMailerError,
InvalidEmailError,
AttachmentError,
AuthenticationError,
ConnectionError,
EmailSendError,
)
try:
send_email(
html_file="email.html",
subject="Hello",
sender_email="you@gmail.com",
app_password="secret",
receiver_email="friend@example.com",
)
except InvalidEmailError as exc:
print(f"Bad address: {exc.address}")
except AttachmentError as exc:
print(f"Attachment issue: {exc.path} — {exc.reason}")
except AuthenticationError:
print("Wrong credentials. Check your App Password.")
except ConnectionError as exc:
print(f"Cannot reach {exc.server}:{exc.port}")
except EmailSendError as exc:
print(f"Failed after {exc.attempts} attempt(s): {exc.reason}")
except SMTPMailerError as exc:
# Catch-all for any other package error
print(f"Email error: {exc}")
API Reference
send_email(**kwargs) -> dict
Send a single HTML email.
| Parameter | Type | Default | Description |
|---|---|---|---|
html_file |
str | None |
None |
Path to .html file (mutually exclusive with html_content) |
html_content |
str | None |
None |
Raw HTML string (mutually exclusive with html_file) |
subject |
str |
"" |
Email subject line |
sender_email |
str |
"" |
From address and SMTP login |
app_password |
str |
"" |
SMTP password / App Password |
receiver_email |
str | list[str] |
"" |
One or more To addresses |
smtp_server |
str |
"smtp.gmail.com" |
SMTP relay hostname |
port |
int |
587 |
TCP port |
use_ssl |
bool |
False |
Use SMTP_SSL (port 465) |
timeout |
int |
30 |
Socket timeout (seconds) |
cc |
list[str] | None |
None |
CC addresses |
bcc |
list[str] | None |
None |
BCC addresses (envelope only) |
attachments |
list[str] | None |
None |
File paths to attach |
retries |
int |
3 |
Total send attempts |
Returns:
{
"success": True,
"recipients": ["user@example.com"],
"subject": "Hello",
"timestamp": "2024-01-01T12:00:00+00:00",
"message_id": "550e8400-e29b-41d4-a716-446655440000",
}
send_batch_email(**kwargs) -> dict
Same parameters as send_email but accepts receiver_emails: list[str] and
per_message_delay: float (default 0.5). Returns a dict keyed by recipient address.
SMTPMailer(sender_email, app_password, ...)
Class-based interface. Constructor accepts sender_email, app_password, smtp_server,
port, use_ssl, timeout, and retries. Exposes .send(**kwargs) and
.send_batch(**kwargs) with the same signatures as the functional API.
Configuration Reference
Retry backoff: delays follow 1 × 2^(attempt-1) seconds — 1 s, 2 s, 4 s, etc.
Attachment limits:
- Allowed:
.pdf,.png,.jpg,.jpeg,.docx,.doc,.txt,.zip,.csv,.xlsx - Max size: 25 MB per file
Security Notes
- Never hard-code credentials. Use environment variables or a secrets manager.
- Passwords are never logged or included in exception messages.
- Sender addresses in logs are masked (
ab***@gmail.com). - BCC addresses are kept out of message headers.
import os
from smtp_mailer import send_email
send_email(
html_file="email.html",
subject="Hello",
sender_email=os.environ["SENDER_EMAIL"],
app_password=os.environ["APP_PASSWORD"],
receiver_email=os.environ["RECEIVER_EMAIL"],
)
Publishing to PyPI
1. Install build tools
pip install build twine
2. Build the distribution
python -m build
This creates dist/smtp_mailer-1.0.0.tar.gz and dist/smtp_mailer-1.0.0-py3-none-any.whl.
3. Upload to TestPyPI first
twine upload --repository testpypi dist/*
Verify the install:
pip install --index-url https://test.pypi.org/simple/ smtp_mailer
4. Upload to PyPI
twine upload dist/*
Tip: Use a PyPI API token (not your password) and store it in
~/.pypircor as theTWINE_PASSWORDenvironment variable.
License
MIT © smtp_mailer contributors
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 smtp_mailer_ezra-1.0.0.tar.gz.
File metadata
- Download URL: smtp_mailer_ezra-1.0.0.tar.gz
- Upload date:
- Size: 24.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
63abb06096266ebaa9ad75d7be7764906cb04928f83af08e2d76e7204579f93c
|
|
| MD5 |
6471efdd400cde4ac62dd83a9aa1e4da
|
|
| BLAKE2b-256 |
eb3814aeff1cdd1cb42b4ed64bdddd71b1290ae94a9662bd12cd7712f708895e
|
File details
Details for the file smtp_mailer_ezra-1.0.0-py3-none-any.whl.
File metadata
- Download URL: smtp_mailer_ezra-1.0.0-py3-none-any.whl
- Upload date:
- Size: 19.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8008c43bd2c0c6ab407a6cc6c240575b14151ee558b3e9a4f56aa0bcc12710b7
|
|
| MD5 |
6624401cd5364ff222c0ddd8ab6b3830
|
|
| BLAKE2b-256 |
dbef27ed3fdcdce852b742d9cc8001534aa04e1e1fc3c244228598a19cc68e1b
|