Skip to main content

smtpkit

PyPI version Python Versions License: MIT Type Checked: typed

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 NullHandler by 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 .env files, environment variables, or typed EmailConfig objects.
  • 📦 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:

  1. Start Mailpit:
    docker compose up -d
    
  2. Set .env to point to localhost:
    EMAIL_SMTP_HOST=localhost
    EMAIL_SMTP_PORT=1025
    EMAIL_USE_TLS=false
    
  3. 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

  1. Build distribution archives:

    python -m build
    
  2. Verify archives with Twine:

    twine check dist/*
    
  3. Upload to TestPyPI (Optional):

    twine upload --repository testpypi dist/*
    
  4. 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)

Source distribution for smtpkit 0.1.1
File Size Uploaded
smtpkit-0.1.1.tar.gz 22.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for smtpkit 0.1.1
File Interpreter ABI Platform
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

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page