Skip to main content

Production-ready Python package for sending HTML emails via SMTP with TLS, SSL, CC/BCC, attachments, and retries.

Project description

smtp_mailer

PyPI version Python Versions License: MIT CI

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

  • ✅ 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_id and timestamp
  • ✅ 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_file and html_content are mutually exclusive — providing both raises a ValueError.


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 ~/.pypirc or as the TWINE_PASSWORD environment variable.


License

MIT © smtp_mailer contributors

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

smtp_mailer_ezra-1.0.0.tar.gz (24.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

smtp_mailer_ezra-1.0.0-py3-none-any.whl (19.8 kB view details)

Uploaded Python 3

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

Hashes for smtp_mailer_ezra-1.0.0.tar.gz
Algorithm Hash digest
SHA256 63abb06096266ebaa9ad75d7be7764906cb04928f83af08e2d76e7204579f93c
MD5 6471efdd400cde4ac62dd83a9aa1e4da
BLAKE2b-256 eb3814aeff1cdd1cb42b4ed64bdddd71b1290ae94a9662bd12cd7712f708895e

See more details on using hashes here.

File details

Details for the file smtp_mailer_ezra-1.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for smtp_mailer_ezra-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8008c43bd2c0c6ab407a6cc6c240575b14151ee558b3e9a4f56aa0bcc12710b7
MD5 6624401cd5364ff222c0ddd8ab6b3830
BLAKE2b-256 dbef27ed3fdcdce852b742d9cc8001534aa04e1e1fc3c244228598a19cc68e1b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page