fix: honor legacy custom async HTTP clients
What changed
Async requests now use an AsyncHTTPClient assigned to resend.default_http_client when the dedicated async setting still holds the automatic client. A replacement assigned to resend.default_async_http_client retains priority.
The module keeps a private reference to the client created at import time. The dispatcher uses that reference to distinguish the automatic default from a user-supplied client. The README explains the selection order and recommends the dedicated setting for new code.
Why
The original async support documented this configuration:
resend.default_http_client = resend.HTTPXClient(timeout=60)
await resend.Emails.send_async(params)
With httpx installed, module initialization also creates default_async_http_client. The dispatcher selected that non-None client before checking the legacy setting. As a result, the request used the automatic client's 30-second timeout instead of the configured 60-second timeout. A custom subclass's request method was also bypassed.
I reproduced this on upstream 4ce466e before changing the dispatcher. The current dedicated-client configuration already worked and remains the preferred configuration.
How it was tested
- Ran the unchanged upstream suite first: 701 passed.
- Added 12 regression and control cases before changing production code: six failed and six passed. After the fix, all 12 passed.
- Tested public
Emails.send_async,Emails.get_asyncandUsage.get_asynccalls through the real SDK dispatcher and HTTPX client.httpx.MockTransportsupplies the HTTP response. Assertions check the selected client, timeout, request body, authentication and idempotency headers, and returned response headers. - Covered explicit dedicated clients, including a plain
HTTPXClient; legacy clients; the automatic default; clearing or restoring the dedicated setting; and missing-client and custom-client error behavior. - Ran the complete suite on Python 3.9.6 and 3.14.6: 713 passed on each. Lint and mypy passed on both versions.
- Built the wheel and source distribution. Installed the wheel in fresh environments and checked imports from
site-packageswith isolated Python execution. Ten checks passed with the async extra; four passed withouthttpx, including custom async clients and synchronous requests. - Replayed the exact patch in a clean upstream export before submission. Full
toxpassed on Python 3.14;tox -e lint,mypy,pypassed on Python 3.9. The four changed files still match the patch after formatting. - Ran the repository's Black → isort sequence on the three changed Python files and confirmed that repeating it left their contents unchanged.
git diff --checkpassed.
Commands for the focused and full checks:
python -m pytest -q tests/async_http_client_selection_test.py
tox
# Python 3.9: lint, mypy and complete test suite
tox -e lint,mypy,py
python -m build
The existing Audiences deprecation warnings remain. Python 3.9 also reports an environment warning because its SSL module uses LibreSSL. Package build warnings concern the existing license classifier and universal-wheel settings.
All HTTP responses in these checks are controlled locally. No request was sent to the live Resend API. The full upstream Python version matrix was not run.
Scope
This change restores legacy async configuration while preserving explicit dedicated-client priority. It does not change the synchronous dispatcher, error mapping, dependency versions or connection lifecycle.