Metadata-Version: 2.4
Name: smtp_mailer-ezra
Version: 1.0.0
Summary: Production-ready Python package for sending HTML emails via SMTP with TLS, SSL, CC/BCC, attachments, and retries.
Author: smtp_mailer contributors
License: MIT
Project-URL: Homepage, https://github.com/smtp-mailer/smtp_mailer
Project-URL: Documentation, https://github.com/smtp-mailer/smtp_mailer#readme
Project-URL: Repository, https://github.com/smtp-mailer/smtp_mailer
Project-URL: Bug Tracker, https://github.com/smtp-mailer/smtp_mailer/issues
Project-URL: Changelog, https://github.com/smtp-mailer/smtp_mailer/blob/main/CHANGELOG.md
Keywords: email,smtp,html,mailer,tls,ssl,attachments
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Communications :: Email
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: email-validator>=2.1
Provides-Extra: dev
Requires-Dist: pytest>=7.4; extra == "dev"
Requires-Dist: pytest-cov>=4.1; extra == "dev"
Requires-Dist: ruff>=0.4; extra == "dev"
Requires-Dist: mypy>=1.10; extra == "dev"
Dynamic: license-file

# smtp_mailer

[![PyPI version](https://badge.fury.io/py/smtp-mailer.svg)](https://badge.fury.io/py/smtp-mailer)
[![Python Versions](https://img.shields.io/pypi/pyversions/smtp-mailer.svg)](https://pypi.org/project/smtp-mailer/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![CI](https://github.com/smtp-mailer/smtp_mailer/actions/workflows/tests.yml/badge.svg)](https://github.com/smtp-mailer/smtp_mailer/actions)

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](#features)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Gmail Example](#gmail-example)
- [Outlook Example](#outlook-example)
- [SSL Example (port 465)](#ssl-example-port-465)
- [Inline HTML Content](#inline-html-content)
- [CC and BCC](#cc-and-bcc)
- [Attachments Example](#attachments-example)
- [Batch Sending](#batch-sending)
- [Context Manager](#context-manager)
- [Error Handling](#error-handling)
- [API Reference](#api-reference)
- [Configuration Reference](#configuration-reference)
- [Security Notes](#security-notes)
- [Publishing to PyPI](#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_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

```bash
pip install smtp_mailer
```

**Requirements:** Python 3.11+

---

## Quick Start

```python
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](https://support.google.com/accounts/answer/185833)
when 2-Step Verification is enabled.

```python
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

```python
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)

```python
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:

```python
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

```python
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

```python
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:

```python
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:

```python
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`:

```python
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:**
```python
{
    "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.

```python
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

```bash
pip install build twine
```

### 2. Build the distribution

```bash
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

```bash
twine upload --repository testpypi dist/*
```

Verify the install:

```bash
pip install --index-url https://test.pypi.org/simple/ smtp_mailer
```

### 4. Upload to PyPI

```bash
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](LICENSE) © smtp_mailer contributors
