All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
- Django email backend (
postmark.django.EmailBackend), gated behind the newdjangoextra (pip install postmark-python[django]). Supports Django 4.2 LTS, 5.2 LTS, 6.0, and 6.1 — including Django 6.0's "modern email API" change. The Postmark payload is built fromEmailMessage's high-level attributes rather thanEmailMessage.message(), so this backend is unaffected by that change.postmark.django.PostmarkEmailMessage/PostmarkEmailMultiAlternatives/PostmarkEmailMixinfor settingtag,metadata, andmessage_stream.postmark.django.pre_send/post_send/on_exceptionsignals.- New settings:
POSTMARK_SERVER_TOKEN,POSTMARK_TEST_MODE,POSTMARK_TRACK_OPENS,POSTMARK_MESSAGE_STREAM. - See the Django Backend wiki page and
examples/django/.
InactiveRecipientExceptionno longer truncates inactive email addresses at the first.in the domain (e.g.john@example.comwas truncated tojohn@example). Handles single and multiple inactive recipients correctly. (Thanks, @bharara.)
- Updated
pytestto9.1.1, addressing PYSEC-2026-1845 (local privilege escalation via a temp-directory race condition). - Updated
respxtest dependency constraint and lockfile. - Updated dev tooling:
mypy,ruff, andpre-commit; reformatted README code examples for theruff0.16 formatter. - CI: updated
actions/setup-pythontov7andgithub/codeql-actionto4.37.4. - CI: free disk space before CodeQL scans to prevent runner out-of-disk failures.
- Security hardening for the repository and release pipeline:
- Dependabot (
.github/dependabot.yml): weekly automated PRs for Python dependency and GitHub Actions updates; minor and patch updates grouped to reduce noise. - CodeQL (
.github/workflows/codeql.yml): SAST scanning on every push, PR, and weekly schedule; results surface in the GitHub Security tab. - Dependency vulnerability scanning (
.github/workflows/security.yml):pip-auditchecks all locked dependencies against OSV/PyPI advisory databases on push, PR, and weekly. - Publish gate (
publish.yml):pip-auditnow runs before every PyPI release; a known-vulnerable dependency blocks the publish job. - Secret detection (
.pre-commit-config.yaml):detect-secretspre-commit hook blocks commits containing hard-coded credentials. SECURITY.md: published security policy with private vulnerability reporting instructions and scope definition.
- Dependabot (
- Updated
idna(transitive dependency viahttpx) from 3.11 to 3.18 to address PYSEC-2026-215, a DoS vulnerability inidna.encode()for arbitrarily large inputs.
- Modernized type hints to Python 3.10+ syntax throughout:
Optional[X]toX | None,Union[X, Y]toX | Y,List[X]tolist[X],Dict[K, V]todict[K, V].AsyncGenerator,Callable, andAwaitablemoved fromtypingtocollections.abc. No functional changes. - Added
target-version = "py310"and theUPruleset to ruff config, enforcing the modernized syntax going forward.
- Migrated
pyproject.tomlpackage metadata from the legacy[tool.poetry]table to the PEP 621[project]table. No functional changes — dependency version constraints are semantically equivalent,[tool.poetry]is retained only for the Poetry-specificpackagesdirective, and the build-system pin is now explicit (poetry-core>=2.4.0,<3.0.0).
postmark/py.typedmarker (PEP 561): type checkers (mypy, pyright, Pylance) in downstream projects now pick up the package's inline annotations. The annotations were already present; the missing marker caused conformant checkers to ignore them.
- Trove classifiers synced with CI: added
Programming Language :: Python :: 3.13,Programming Language :: Python :: 3.14, andTyping :: Typed.
- Updated dev tooling: mypy 2.x, ruff 0.15.x, Poetry 2.4.1, and pre-commit hooks
postmark.sync: Gunicorn and Odoo devs, rejoice! ...the module-level event loop thread is now fork-safe. Previously, importingpostmark.syncbefore a process fork caused child processes to inherit a stale event loop with no running thread, causing all sync API calls to hang indefinitely. The loop and thread are now created lazily on first use and recreated automatically when a PID change is detected. (Good eye, @yibudak.)
SyncServerClientandSyncAccountClient— synchronous wrappers around the async clients, backed by a single daemon thread with a persistent asyncio event loop. Enables SDK use in scripts, Flask apps, and Jupyter notebooks withoutasync/await. HTTP connection pooling is retained across calls for performance.- Examples reorganized into
examples/async/andexamples/sync/directories with parallel coverage, plus two new sync-only examples (send_sync_simple.py,send_sync_batch.py). - 31 new tests for sync client behavior (
tests/test_sync_client.py).
- Upgraded
httpxdependency to0.28.1. - CI: expanded test matrix to include Python 3.13 and 3.14.
- CI: updated GitHub Actions to
actions/checkout@v6,actions/setup-python@v6,actions/cache@v5, and Poetry2.3.4.
- README: logo image now uses an absolute raw GitHub URL so it renders correctly on the PyPI project page.
__version__resolves from thepostmark-pythondistribution metadata soX-Postmark-Client-Versionmatches afterpip install postmark-python(falls back to0.0.0when not installed as a package).
poetry.lockis tracked in version control again (removed from.gitignore) for reproducible installs and CI cache keys.- README: removed the misleading note about a future PyPI distribution under the name
postmark.
- Timeout error message now uses the client’s configured timeout with clearer numeric formatting (
:g), for bothServerClientandAccountClient. - Postmark API
ErrorCodevalues from JSON are coerced tointwhen sent as numeric strings; invalid values and booleans map toNoneso exception mapping stays reliable. - README quick start no longer imports
python-dotenv(a dev-only dependency); optional.envloading is described in a comment instead.
- PyPI distribution renamed from
postmarktopostmark-pythonto avoid clashing with the unrelatedpostmarkpackage on PyPI. The import name remainspostmark. - Trove classifier updated from Alpha to Beta (
Development Status :: 4 - Beta).
- Project URLs for PyPI metadata: repository, homepage (official libraries), documentation (wiki), and Issues link.
- Client identification on every request:
User-AgentasPython/{major}.{minor}.{micro},X-Postmark-Clientaspostmark-python,X-Postmark-Client-Versionas the installed SDK version, and a freshX-Postmark-Correlation-Id(UUID) per HTTP request. X-Request-Idfrom Postmark responses is now stored asrequest_idon allPostmarkAPIExceptionsubclasses and included in the exception__str__output when present — enabling direct support escalations.request_idincluded in structured log records for both successful requests and API errors.- Structured
extra={}fields on all log calls (method,endpoint,status_code,duration_ms,error_code,postmark_message,request_id) for compatibility with Datadog, Splunk, and other log aggregators. duration_mstiming on every request log record (success, error, and timeout).
- Upgraded
pytest-asyncioto^1.0.0and setasyncio_mode = "auto"to eliminate deprecation warnings on Python 3.12+.
- Initial release of the SDK.
ServerClientandAccountClientwith authentication, configurable retries, timeout, and optionalbase_urloverride for local mock servers.- Managers for outbound/inbound messages, bounces, templates, streams, suppressions, webhooks, stats, domains, sender signatures, and data removals.
- Async pagination via
paginate()utility;stream()methods onOutboundManagerandBounceManager. - Typed request/response models backed by Pydantic v2.
- Pytest test suite.