Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
7ec95f6
build(deps): bump Python to >=3.11 and introduce httpx2
skupriienko-mailgun Jul 20, 2026
40ce470
feat(security): implement SpamGuard, IdempotencyGuard, and native IDN…
skupriienko-mailgun Jul 20, 2026
2ed2623
feat(config): introduce deterministic RetryPolicy engine
skupriienko-mailgun Jul 20, 2026
47100f0
feat(builders): add ChunkedStreamer, explicit CIDs, and pipeline guar…
skupriienko-mailgun Jul 20, 2026
fd88531
feat(client): implement liveness probes, explicit retries, and Sandbo…
skupriienko-mailgun Jul 20, 2026
0cf9c1b
refactor(compat): migrate httpx imports to internal compat wrapper
skupriienko-mailgun Jul 20, 2026
05f9e5d
docs: update README and expand advanced builder examples
skupriienko-mailgun Jul 20, 2026
9c7678e
test: fortify test suites for new features and optimize execution time
skupriienko-mailgun Jul 20, 2026
d58eefe
feat: add Pydantic extension, new sandbox and extension examples
skupriienko-mailgun Jul 20, 2026
b0a3f02
feat: improve Pydantic extension and examples
skupriienko-mailgun Jul 20, 2026
3b861c9
test(fuzz): add new fuzz tests, imporve existing, update fuzz.dict
skupriienko-mailgun Jul 20, 2026
014b064
docs: enable some examples
skupriienko-mailgun Jul 20, 2026
7c46579
test: update assertion to expect 3 retries
skupriienko-mailgun Jul 20, 2026
73b095e
ci(workflows): use py314 as a default, remove py310 from the matrix
skupriienko-mailgun Jul 20, 2026
e019dda
Merge branch 'main' into feat/sdk-modernization-phase-2
skupriienko-mailgun Jul 20, 2026
d61eea2
refactor: drop Python 3.10 support and remove typing extensions
skupriienko-mailgun Jul 20, 2026
fc2097f
build(deps): introduce Pydantic as an optional dependency
skupriienko-mailgun Jul 20, 2026
f14b0e9
test: add unit tests for streaming, retry policy, and security bounda…
skupriienko-mailgun Jul 20, 2026
ddd94c8
docs: update documentation and changelog for version 1.9.0
skupriienko-mailgun Jul 20, 2026
ddefb53
docs(release): update README
skupriienko-mailgun Jul 20, 2026
c380ead
chore: replace Enum with StrEnum
skupriienko-mailgun Jul 20, 2026
2685505
test(fuzz): update fuzz.dict
skupriienko-mailgun Jul 20, 2026
00d6d75
Merge branch 'main' into feat/sdk-modernization-phase-2
skupriienko-mailgun Jul 21, 2026
655fc42
fix(security): harden payload processing and boundary validation
skupriienko-mailgun Jul 22, 2026
724ff2a
fix(routing): resolve url corruption and core async iteration bugs
skupriienko-mailgun Jul 22, 2026
89f2c1d
fix(observability): resolve logger runtime crash and update sandbox d…
skupriienko-mailgun Jul 22, 2026
64a0863
test(fuzz): expand fuzzing harnesses and regression suites
skupriienko-mailgun Jul 22, 2026
fa23ae8
test(fuzz): add fuzz semantic payload test
skupriienko-mailgun Jul 22, 2026
919c531
Merge branch 'feat/sdk-modernization-phase-2' of id_ed25519_mailwork:…
skupriienko-mailgun Jul 22, 2026
04e83b0
Potential fix for pull request finding 'Unused import'
skupriienko-mailgun Jul 22, 2026
2a8d441
feat: move LocalSandobx to extensions
skupriienko-mailgun Jul 22, 2026
42e81e4
fix(tests): update dry_run and timeout tests
skupriienko-mailgun Jul 22, 2026
413456d
test(fuzz): use sys.stderr instead of opening file
skupriienko-mailgun Jul 22, 2026
47e4817
style: address mypy linting issues
skupriienko-mailgun Jul 22, 2026
d77cbe1
style: address ruff & mypy linting issues
skupriienko-mailgun Jul 22, 2026
61324b9
ci: add default_stages to pre-commit config
skupriienko-mailgun Jul 22, 2026
e5f7db8
docs(release): update CHANGELOG
skupriienko-mailgun Jul 22, 2026
97019b8
docs(release): update README
skupriienko-mailgun Jul 22, 2026
7ff57c4
test(fuzz): update fuzz.dict
skupriienko-mailgun Jul 22, 2026
21df13b
fix(security): comprehensive security guardrails and network resilien…
skupriienko-mailgun Jul 23, 2026
9e49b62
test: Hardening ChunkedStreamer in mailgun/builders.py using getattr …
skupriienko-mailgun Jul 23, 2026
ee5ea0b
refactor(sdk): modernize client lifecycle management, sandbox extensi…
skupriienko-mailgun Jul 23, 2026
aa55c71
Potential fix for pull request finding '`__del__` is called explicitly'
skupriienko-mailgun Jul 23, 2026
ae40dac
docs(release): update SECURITY
skupriienko-mailgun Jul 23, 2026
7d72f92
Potential fix for pull request finding 'Unused local variable'
skupriienko-mailgun Jul 23, 2026
3a0dc38
fix(docs): clean up and fix examples
skupriienko-mailgun Jul 25, 2026
8786898
chore: get rid of the local sandbox
skupriienko-mailgun Jul 25, 2026
813c0ae
docs: clean up README and fix code examples
skupriienko-mailgun Jul 25, 2026
f59f9b0
docs: clean up README and fix code examples
skupriienko-mailgun Jul 25, 2026
8b1cd4b
Potential fix for pull request finding 'Unused import'
skupriienko-mailgun Jul 25, 2026
e3938ef
fix(client): prevent recursion loop in client destructor and attribut…
skupriienko-mailgun Jul 26, 2026
c4c6e54
fix(endpoints): fix retry loop control flow, response scope leaks, an…
skupriienko-mailgun Jul 26, 2026
48b9c94
fix(security): handle OverflowError on massive timestamps in webhook …
skupriienko-mailgun Jul 26, 2026
2c87d53
test(regression): add regression test for webhook timestamp float ove…
skupriienko-mailgun Jul 26, 2026
42546d1
refactor(builders): enforce keyword-only parameters in ChunkedStreamer
skupriienko-mailgun Jul 26, 2026
d73c4b9
fix(endpoints): preserve developer types in paginated filter parameters
skupriienko-mailgun Jul 26, 2026
ad18c1e
test(logger): cap RedactingFilter recursion depth and update tests
skupriienko-mailgun Jul 26, 2026
0706c13
fix(guards): enforce SpamGuard memory limits and IdempotencyGuard str…
skupriienko-mailgun Jul 26, 2026
0611f61
test(client): expand sync and async client context manager and ping c…
skupriienko-mailgun Jul 26, 2026
33a8277
test(property): update Hypothesis invariants for security rejection b…
skupriienko-mailgun Jul 26, 2026
b417258
chore(config): bump mypy python version target to 3.12
skupriienko-mailgun Jul 26, 2026
3ce2622
test(property): harden hypothesis test suite and handle security reje…
skupriienko-mailgun Jul 26, 2026
cbcc555
refactor(endpoints): extract helper methods and fix unit test failures
skupriienko-mailgun Jul 26, 2026
ed8c413
Potential fix for pull request finding 'Empty except'
skupriienko-mailgun Jul 26, 2026
eeea761
Potential fix for pull request finding 'Unused import'
skupriienko-mailgun Jul 26, 2026
ebb775c
Potential fix for pull request finding '`__del__` is called explicitly'
skupriienko-mailgun Jul 26, 2026
8b92019
Potential fix for pull request finding 'Unused import'
skupriienko-mailgun Jul 26, 2026
0520615
Potential fix for pull request finding 'Unused global variable'
skupriienko-mailgun Jul 26, 2026
fbd07cb
Potential fix for pull request finding 'CodeQL / Incomplete URL subst…
skupriienko-mailgun Jul 26, 2026
bdeff0d
test(property): normalize paths containing consecutive slashes
skupriienko-mailgun Jul 26, 2026
0bd1d17
chore: apply an isort-style sorting to ''__all__'
skupriienko-mailgun Jul 26, 2026
180d058
fix(endpoints): remove redundant exception objects from logger.except…
skupriienko-mailgun Jul 26, 2026
73bee26
Merge branch 'main' of id_ed25519_mailwork:mailgun/mailgun-python int…
skupriienko-mailgun Jul 26, 2026
01684a5
fix(builders): enforce bounds on ChunkedStreamer and prevent position…
skupriienko-mailgun Jul 27, 2026
daeca7f
test(fuzz): harden fuzzing suite with new targets and auto-format
skupriienko-mailgun Jul 27, 2026
e3f935a
fix(client): add explanatory comment to empty except block in Client.…
skupriienko-mailgun Jul 27, 2026
b6a484a
fix(client): use contextlib.suppress in destructors to satisfy ruff S…
skupriienko-mailgun Jul 27, 2026
92c5731
test(builders): replace explicit __del__ call with idempotent close()…
skupriienko-mailgun Jul 27, 2026
f99b4f2
fix(security,tests): harden path validation, support macOS temp paths…
skupriienko-mailgun Jul 27, 2026
364948e
Potential fix for pull request finding 'Unused import'
skupriienko-mailgun Jul 27, 2026
9de5b61
docs(release): update CHANGELOG
skupriienko-mailgun Jul 27, 2026
480bb83
Potential fix for pull request finding 'Unused import'
skupriienko-mailgun Jul 27, 2026
6975bec
fix(logger): harden log redacting filter and add regression tests
skupriienko-mailgun Jul 27, 2026
3f8bc64
test(perf): improve perf and boot tests
skupriienko-mailgun Jul 27, 2026
97fc96a
docs(release): update PERFORMANCE.md
skupriienko-mailgun Jul 27, 2026
fc8223d
Potential fix for pull request finding 'Unused import'
skupriienko-mailgun Jul 27, 2026
606eb8b
build(release): update conda-build recipe
skupriienko-mailgun Jul 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/commit_checks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: '3.13' # Specify a Python version explicitly
python-version: '3.14' # Specify a Python version explicitly
- uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1

test:
Expand All @@ -30,7 +30,7 @@ jobs:
fail-fast: false
matrix:
os: ["ubuntu-latest", "macos-latest", "windows-latest"]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
python-version: ["3.11", "3.12", "3.13", "3.14"]
env:
APIKEY: ${{ secrets.APIKEY }}
DOMAIN: ${{ secrets.DOMAIN }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr_validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: '3.13'
python-version: '3.14'

- name: Build package
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: '3.13'
python-version: '3.14'

- name: Install build tools
run: pip install --upgrade build setuptools wheel setuptools-scm twine
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v5
with:
python-version: "3.13"
python-version: "3.14"
cache: 'pip'
- run: python -m pip install --upgrade pip
- run: pip install ruff bandit mypy pip-audit
Expand Down Expand Up @@ -51,7 +51,7 @@ jobs:
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v5
with: { python-version: "3.13" }
with: { python-version: "3.14" }
- run: python -m pip install --upgrade pip
- run: pip install pip-audit
- run: pip-audit --strict
Expand Down
3 changes: 3 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ ci:
skip: []
submodules: false

# Run hooks on staged files by default, ignoring the commit-msg phase
default_stages: [pre-commit]

# .pre-commit-config.yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
Expand Down
23 changes: 22 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,28 @@

We [keep a changelog.](http://keepachangelog.com/)

## [Unreleased]
## [Unreleased] (v1.9.0)

### Added

- **IdempotencyGuard**: Implemented exactly-once email delivery mechanisms (SHA-256 fingerprinting on payloads and stream buffers) to prevent duplicate sends during network partitions.
- **RetryPolicy**: Introduced a flexible retry configuration with stateless exponential backoff and full jitter to mitigate the "Thundering Herd" effect.
- **`httpx2` Compatibility**: Added native support for the modern `httpx2` engine via the `mailgun._httpx_compat.py` bridge, featuring a transparent fallback mechanism for legacy `httpx` environments.
- **`mailgun.ext` Ecosystem**: Introduced strict Pydantic v2 payload schemas (e.g., `SendMessageSchema`) and framework-bound extensions.
- **ChunkedStreamer**: Added memory-safe lazy streaming for large file attachments (up to 25MB) using 512KB partitions to lock memory usage to $O(1)$.
- **SpamGuard**: Added a zero-network static HTML analyzer to preemptively flag deliverability risks (XSS, missing alt tags) before dispatching requests to Mailgun.
- **IDN Routing**: Implemented `normalize_domain` to natively convert Internationalized Domain Names to safe RFC 3490 Punycode.
- Added `DeliverabilityError` exception to handle SpamGuard rule violations.

### Changed

- **[BREAKING CHANGE]** Dropped support for Python 3.10. The SDK now strictly requires Python 3.11 through 3.14.
- Purged the `typing-extensions` dependency from the package, replacing it with standard library `typing` equivalents (`Self`, `TypedDict`, `NotRequired`).

### Fixed

- Fixed strict typing issues, circular import risks, and unawaited coroutines in `AsyncClient.ping()`.
- Hardened `SecurityGuard.sanitize_timeout` against infinite/NaN injection (CWE-400) and cleaned up exception handling blocks to eliminate silent failure anti-patterns.

## v1.8.0 - 2026-07-20

Expand Down
29 changes: 21 additions & 8 deletions PERFORMANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,35 @@ If you are contributing to this repository, please review these principles befor

String manipulation, dynamic imports (`importlib`), and sequential regex evaluations are historically slow in Python.

- **Static Dispatch:** Base API URLs (`/v3`, `/v4`, etc.) and handler functions are pre-mapped in immutable dictionaries (`EXACT_ROUTES`, `PREFIX_ROUTES`).
- **Impact:** The SDK completely avoids string concatenation and dynamic resolution during high-volume request loops, increasing routing speed by over **12x**.
- **Static Dispatch:** Base API URLs and handler functions are pre-mapped in immutable dictionaries (`EXACT_ROUTES`, `PREFIX_ROUTES`).
- **Impact:** The SDK completely avoids string concatenation and dynamic resolution during high-volume request loops, sustaining over **1 million routing operations per second**.

### 2. High-Concurrency Transport Layer (`httpx` & `__slots__`)

- **Native AsyncIO & Connection Pooling:** The `AsyncClient` allows for true non-blocking throughput. Both clients enforce connection pooling to prevent OS socket exhaustion.
- **Memory Density (`__slots__`):** By defining `__slots__` on `Endpoint` and `Client` classes, we block Python from creating dynamic `__dict__` hash tables. This drastically reduces the RAM footprint of each instantiated client and lowers *Garbage Collection (GC)* pauses during concurrent workloads.
- **Memory Density (`__slots__`):** By defining `__slots__` on `Endpoint` and `Client` classes, we block Python from creating dynamic `__dict__` hash tables, minimizing RAM footprint and garbage collection pauses.

### 3. Cold-Boot Initialization & Lazy Loading
### 3. Streamlined Cold-Boot Initialization

- **Deferred Regex Compilation:** Legacy SDK versions compiled multiple `re.Pattern` objects upon module import. By wrapping these in `@functools.lru_cache(maxsize=1)` and returning an immutable `MappingProxyType`, the SDK defers expensive AST parsing until the exact moment it is needed, shaving ~15-30ms off the initial application startup time.
- **Optimized Import Paths:** By reducing unnecessary module imports and lazy-loading heavy components, the initialization sequence executes **~45,000 fewer function calls** than baseline versions.
- **Impact:** Speeds up cold starts, making the SDK exceptionally well-suited for serverless environments (AWS Lambda, Google Cloud Functions).

### 4. Zero-Regression Security & Context Generation (v1.8.0+)
### 4. Zero-Regression Security Guardrails

- **Centralized Pre-computation:** The `Config` object and unified `_prepare_request` architecture consolidate header merging, authentication resolution, and schema logging into a single invariant block, dropping per-request CPU cycles.
- **Enterprise-Grade TLS Latency Tradeoff:** The SDK explicitly generates a hardened `ssl.SSLContext()` to enforce `TLSv1.2+` and mitigate MITM downgrade attacks. This introduces a strict, one-time boot cost (loading OS certificates via `set_default_verify_paths`), but ensures the active event loop and hot path remain exceptionally fast and safe.
- Core request preparation incorporates `SecurityGuard`, `IdempotencyGuard`, and `RetryPolicy` with virtually zero performance penalty (~78 ns security tax on routing). We intentionally trade these microscopic CPU cycles to provide enterprise-grade safety.

______________________________________________________________________

## Benchmarks (v1.8.0 vs. v1.9.0)

| Metric | v1.8.0 (Baseline) | v1.9.0 (Current) | Delta / Notes |
| :-------------------------- | :---------------- | :--------------- | :------------------------------------- |
| **Cold Boot Time** | ~0.316 s | **~0.230 s** | **~27.2% Faster** (Optimized imports) |
| **Routing Speed (Mean)** | ~0.84 µs | **~0.92 µs** | **+78 ns** (Security validation tax) |
| **Async Throughput (Mean)** | ~3.72 ms | **~3.68 ms** | **Stable** (Parity) |
| **Sync Throughput (Mean)** | ~9.84 ms | **~10.39 ms** | **+0.55 ms** (Thread coordination tax) |

*Note: Tests were executed on CPython 3.13 (Apple M4 Pro, Darwin ARM64-bit) in an isolated environment.*

______________________________________________________________________

Expand Down
Loading
Loading