-
Notifications
You must be signed in to change notification settings - Fork 14
fix: rename http response headers key to http_headers to avoid collision #192
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -1,44 +1,80 @@ | ||||||||
| """ | ||||||||
| Example demonstrating how to access response headers. | ||||||||
| Example demonstrating the three different types of headers in the Resend Python SDK: | ||||||||
|
|
||||||||
| Response headers include useful information like rate limits, request IDs, etc. | ||||||||
| 1. Email headers (SendParams["headers"]): Custom MIME headers added to the outgoing | ||||||||
| email itself, visible to the recipient's mail client (e.g. X-Entity-Ref-ID). | ||||||||
|
|
||||||||
| 2. HTTP response headers (response["http_headers"]): HTTP-level metadata returned | ||||||||
| by the Resend API, such as rate limit info and request IDs. These are injected | ||||||||
| by the SDK and are never part of the email content. | ||||||||
|
|
||||||||
| 3. Inbound email MIME headers (email["headers"]): MIME headers present on a received | ||||||||
| email, returned as part of the API response body (e.g. X-Mailer, DKIM-Signature). | ||||||||
| """ | ||||||||
|
|
||||||||
| import os | ||||||||
|
|
||||||||
| import resend | ||||||||
|
|
||||||||
| if not os.environ["RESEND_API_KEY"]: | ||||||||
| raise EnvironmentError("RESEND_API_KEY is missing") | ||||||||
| resend.api_key = os.environ["RESEND_API_KEY"] | ||||||||
|
|
||||||||
| # --- Example 1: Custom email headers (part of the outgoing email itself) --- | ||||||||
|
|
||||||||
| params: resend.Emails.SendParams = { | ||||||||
| "from": "[email protected]", | ||||||||
| "to": ["[email protected]"], | ||||||||
| "subject": "Hello from Resend", | ||||||||
| "html": "<strong>Hello, world!</strong>", | ||||||||
| "headers": { | ||||||||
| "X-Entity-Ref-ID": "123456789", | ||||||||
| }, | ||||||||
| } | ||||||||
|
|
||||||||
| resp: resend.Emails.SendResponse = resend.Emails.send(params) | ||||||||
| print(f"Email sent! ID: {resp['id']}") | ||||||||
|
|
||||||||
| if "headers" in resp: | ||||||||
| print(f"Request ID: {resp['headers'].get('x-request-id')}") | ||||||||
| print(f"Rate limit: {resp['headers'].get('x-ratelimit-limit')}") | ||||||||
| print(f"Rate limit remaining: {resp['headers'].get('x-ratelimit-remaining')}") | ||||||||
| print(f"Rate limit reset: {resp['headers'].get('x-ratelimit-reset')}") | ||||||||
| # --- Example 2: HTTP response headers (SDK metadata, not part of the email) --- | ||||||||
|
|
||||||||
| if "http_headers" in resp: | ||||||||
| print(f"Rate limit: {resp['http_headers'].get('ratelimit-limit')}") | ||||||||
| print(f"Rate limit remaining: {resp['http_headers'].get('ratelimit-remaining')}") | ||||||||
| print(f"Rate limit reset: {resp['http_headers'].get('ratelimit-reset')}") | ||||||||
|
|
||||||||
| # --- Example 3: Inbound email MIME headers (from a received email response body) --- | ||||||||
|
|
||||||||
| # Replace with a real received email ID | ||||||||
| received_email_id = os.environ.get("RECEIVED_EMAIL_ID", "") | ||||||||
|
|
||||||||
| if received_email_id: | ||||||||
| received: resend.ReceivedEmail = resend.Emails.Receiving.get( | ||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: Custom agent: API Key Permission Check SDK Methods
Prompt for AI agents
Suggested change
|
||||||||
| email_id=received_email_id | ||||||||
| ) | ||||||||
|
|
||||||||
| # email["headers"] — MIME headers of the inbound email, part of the API response body. | ||||||||
| # Completely separate from http_headers injected by the SDK. | ||||||||
| if received.get("headers"): | ||||||||
| print("Inbound email MIME headers:") | ||||||||
| for name, value in received["headers"].items(): | ||||||||
| print(f" {name}: {value}") | ||||||||
|
|
||||||||
| # http_headers are also available on received email responses | ||||||||
| if received.get("http_headers"): | ||||||||
| print( | ||||||||
| f"Rate limit remaining: {received['http_headers'].get('ratelimit-remaining')}" | ||||||||
| ) | ||||||||
| else: | ||||||||
| print("Set RECEIVED_EMAIL_ID env var to run the inbound email headers example.") | ||||||||
|
|
||||||||
| print("\n") | ||||||||
| print("Example 3: Rate limit tracking") | ||||||||
| # --- Example 4: Rate limit tracking via HTTP response headers --- | ||||||||
|
|
||||||||
|
|
||||||||
| def send_with_rate_limit_check(params: resend.Emails.SendParams) -> str: | ||||||||
| """Example function showing how to track rate limits.""" | ||||||||
| response = resend.Emails.send(params) | ||||||||
|
|
||||||||
| # Access headers via dict key | ||||||||
| headers = response.get("headers", {}) | ||||||||
| remaining = headers.get("x-ratelimit-remaining") | ||||||||
| limit = headers.get("x-ratelimit-limit") | ||||||||
| http_headers = response.get("http_headers", {}) | ||||||||
| remaining = http_headers.get("ratelimit-remaining") | ||||||||
| limit = http_headers.get("ratelimit-limit") | ||||||||
|
|
||||||||
| if remaining and limit: | ||||||||
| print(f"Rate limit usage: {int(limit) - int(remaining)}/{limit}") | ||||||||
|
|
||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| __version__ = "2.24.0" | ||
| __version__ = "2.25.0" | ||
|
|
||
|
|
||
| def get_version() -> str: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| from unittest.mock import MagicMock | ||
| from unittest.mock import MagicMock, Mock | ||
|
|
||
| import resend | ||
| from resend import EmailsReceiving | ||
|
|
@@ -485,3 +485,65 @@ def test_email_send_with_template_and_variables(self) -> None: | |
| } | ||
| email: resend.Emails.SendResponse = resend.Emails.send(params) | ||
| assert email["id"] == "49a3999c-0ce1-4ea6-ab68-afcd6dc2e794" | ||
|
|
||
| def test_email_send_with_custom_headers(self) -> None: | ||
| self.set_mock_json( | ||
| { | ||
| "id": "49a3999c-0ce1-4ea6-ab68-afcd6dc2e794", | ||
| } | ||
| ) | ||
| params: resend.Emails.SendParams = { | ||
| "to": "[email protected]", | ||
| "from": "[email protected]", | ||
| "subject": "subject", | ||
| "html": "html", | ||
| "headers": { | ||
| "X-Entity-Ref-ID": "123456", | ||
| }, | ||
| } | ||
| email: resend.Emails.SendResponse = resend.Emails.send(params) | ||
| assert email["id"] == "49a3999c-0ce1-4ea6-ab68-afcd6dc2e794" | ||
|
|
||
|
|
||
| import unittest as _unittest | ||
|
|
||
|
|
||
| class TestEmailHeadersRegression(_unittest.TestCase): | ||
| """ | ||
| Tests that mock at the HTTP client level to exercise request.py's injection | ||
| code. ResendBaseTest mocks make_request directly, which bypasses that code | ||
| and would not have caught the v2.23.0 regression. | ||
| """ | ||
|
|
||
| def setUp(self) -> None: | ||
| resend.api_key = "re_123" | ||
|
|
||
| def test_receiving_get_email_headers_not_overwritten_by_http_headers(self) -> None: | ||
| mock_client = Mock() | ||
| mock_client.request.return_value = ( | ||
| b'{"object":"inbound","id":"67d9bcdb-5a02-42d7-8da9-0d6feea18cff",' | ||
| b'"to":["[email protected]"],"from":"[email protected]",' | ||
| b'"created_at":"2023-04-07T23:13:52.669661+00:00","subject":"Test",' | ||
| b'"html":null,"text":"hello","bcc":null,"cc":null,"reply_to":null,' | ||
| b'"message_id":"<msg123>","headers":{"X-Custom":"email-value"},' | ||
| b'"attachments":[]}', | ||
| 200, | ||
| { | ||
| "content-type": "application/json", | ||
| "x-request-id": "req_abc123", | ||
| }, | ||
| ) | ||
|
|
||
| original_client = resend.default_http_client | ||
| resend.default_http_client = mock_client | ||
|
|
||
| try: | ||
| email: resend.ReceivedEmail = resend.Emails.Receiving.get( | ||
| email_id="67d9bcdb-5a02-42d7-8da9-0d6feea18cff", | ||
| ) | ||
| # Email MIME headers must survive the HTTP headers injection | ||
| assert email["headers"] == {"X-Custom": "email-value"} | ||
| # HTTP response headers are available separately | ||
| assert email["http_headers"]["x-request-id"] == "req_abc123" | ||
| finally: | ||
| resend.default_http_client = original_client | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -48,11 +48,11 @@ def test_email_send_response_includes_headers(self) -> None: | |
| assert response.get("from") == "[email protected]" | ||
|
|
||
| # Verify new feature - headers are accessible via dict key | ||
| assert "headers" in response | ||
| assert response["headers"]["x-request-id"] == "req_abc123" | ||
| assert response["headers"]["x-ratelimit-limit"] == "100" | ||
| assert response["headers"]["x-ratelimit-remaining"] == "95" | ||
| assert response["headers"]["x-ratelimit-reset"] == "1699564800" | ||
| assert "http_headers" in response | ||
| assert response["http_headers"]["x-request-id"] == "req_abc123" | ||
| assert response["http_headers"]["x-ratelimit-limit"] == "100" | ||
| assert response["http_headers"]["x-ratelimit-remaining"] == "95" | ||
| assert response["http_headers"]["x-ratelimit-reset"] == "1699564800" | ||
|
|
||
| finally: | ||
| # Restore original HTTP client | ||
|
|
@@ -82,8 +82,8 @@ def test_list_response_headers(self) -> None: | |
| assert isinstance(response, dict) | ||
| assert "data" in response | ||
| # Headers are injected into the dict | ||
| assert "headers" in response | ||
| assert response["headers"]["x-request-id"] == "req_xyz" | ||
| assert "http_headers" in response | ||
| assert response["http_headers"]["x-request-id"] == "req_xyz" | ||
|
|
||
| finally: | ||
| resend.default_http_client = original_client | ||
Uh oh!
There was an error while loading. Please reload this page.