e2a 4.0.1__tar.gz → 5.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {e2a-4.0.1 → e2a-5.2.0}/.gitignore +3 -0
- e2a-5.2.0/CHANGELOG.md +381 -0
- e2a-5.2.0/PKG-INFO +448 -0
- e2a-5.2.0/README.md +410 -0
- {e2a-4.0.1 → e2a-5.2.0}/pyproject.toml +22 -4
- e2a-5.2.0/scripts/generate-oag.sh +84 -0
- e2a-5.2.0/src/e2a/__init__.py +123 -0
- e2a-5.2.0/src/e2a/v1/__init__.py +157 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/_retry.py +34 -16
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/client.py +377 -116
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/errors.py +141 -17
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/__init__.py +131 -15
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/api/__init__.py +1 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/api/account_api.py +133 -39
- e2a-5.2.0/src/e2a/v1/generated/api/agents_api.py +3430 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/api/conversations_api.py +1 -1
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/api/domains_api.py +60 -19
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/api/events_api.py +37 -37
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/api/messages_api.py +320 -244
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/api/meta_api.py +1 -1
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/api/reviews_api.py +86 -34
- e2a-4.0.1/src/e2a/v1/generated/api/agents_api.py → e2a-5.2.0/src/e2a/v1/generated/api/templates_api.py +359 -321
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/api/webhooks_api.py +126 -18
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/api_client.py +1 -1
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/configuration.py +1 -1
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/exceptions.py +1 -1
- e2a-5.2.0/src/e2a/v1/generated/models/__init__.py +153 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/account_user_view.py +14 -1
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/account_view.py +19 -6
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/agent_identity.py +28 -11
- e2a-5.2.0/src/e2a/v1/generated/models/agent_suppression_added_data.py +102 -0
- e2a-5.2.0/src/e2a/v1/generated/models/agent_suppression_view.py +107 -0
- e2a-5.2.0/src/e2a/v1/generated/models/agent_view.py +113 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/api_key_export_entry.py +14 -1
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/api_key_view.py +19 -6
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/approve_request.py +25 -11
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/attachment.py +16 -3
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/attachment_meta_view.py +20 -5
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/attachment_view.py +19 -4
- e2a-5.2.0/src/e2a/v1/generated/models/authentication.py +120 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/conversation_detail_view.py +19 -6
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/conversation_summary_view.py +19 -6
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/create_agent_request.py +18 -4
- e2a-5.2.0/src/e2a/v1/generated/models/create_agent_suppression_request.py +101 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/create_api_key_request.py +19 -5
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/create_api_key_response.py +19 -6
- e2a-5.2.0/src/e2a/v1/generated/models/create_template_request.py +110 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/create_webhook_request.py +19 -6
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/create_webhook_response.py +15 -2
- e2a-5.2.0/src/e2a/v1/generated/models/delete_agent_result.py +104 -0
- e2a-5.2.0/src/e2a/v1/generated/models/delete_api_key_result.py +102 -0
- e2a-5.2.0/src/e2a/v1/generated/models/delete_domain_result.py +102 -0
- e2a-5.2.0/src/e2a/v1/generated/models/delete_message_result.py +102 -0
- e2a-5.2.0/src/e2a/v1/generated/models/delete_suppression_result.py +102 -0
- e2a-5.2.0/src/e2a/v1/generated/models/delete_template_result.py +102 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/delete_user_data_result.py +22 -3
- e2a-5.2.0/src/e2a/v1/generated/models/delete_webhook_result.py +102 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/delivery_status_json.py +14 -1
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/deployment_info_view.py +14 -1
- e2a-5.2.0/src/e2a/v1/generated/models/dkim_result.py +122 -0
- e2a-5.2.0/src/e2a/v1/generated/models/dmarc_result.py +117 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/dns_record.py +16 -3
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/domain.py +17 -4
- e2a-5.2.0/src/e2a/v1/generated/models/domain_sending_failed_data.py +104 -0
- e2a-5.2.0/src/e2a/v1/generated/models/domain_sending_verified_data.py +102 -0
- e2a-5.2.0/src/e2a/v1/generated/models/domain_suppression_added_data.py +106 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/domain_view.py +21 -2
- e2a-5.2.0/src/e2a/v1/generated/models/email_bounced_data.py +114 -0
- e2a-5.2.0/src/e2a/v1/generated/models/email_complained_data.py +110 -0
- e2a-5.2.0/src/e2a/v1/generated/models/email_delivered_data.py +110 -0
- e2a-5.2.0/src/e2a/v1/generated/models/email_failed_data.py +126 -0
- e2a-5.2.0/src/e2a/v1/generated/models/email_received_data.py +161 -0
- e2a-5.2.0/src/e2a/v1/generated/models/email_sent_data.py +122 -0
- e2a-5.2.0/src/e2a/v1/generated/models/error_body.py +106 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/error_envelope.py +14 -1
- e2a-5.2.0/src/e2a/v1/generated/models/event_envelope.py +107 -0
- e2a-4.0.1/src/e2a/v1/generated/models/event_json.py → e2a-5.2.0/src/e2a/v1/generated/models/event_view.py +12 -12
- e2a-5.2.0/src/e2a/v1/generated/models/field_error.py +102 -0
- e2a-5.2.0/src/e2a/v1/generated/models/forward_request.py +129 -0
- e2a-5.2.0/src/e2a/v1/generated/models/hold_reason_view.py +110 -0
- e2a-5.2.0/src/e2a/v1/generated/models/limit_exceeded_details.py +108 -0
- e2a-5.2.0/src/e2a/v1/generated/models/limit_exceeded_envelope.py +104 -0
- e2a-5.2.0/src/e2a/v1/generated/models/limit_exceeded_error_body.py +110 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/limits_caps_view.py +16 -3
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/limits_usage_view.py +16 -3
- e2a-5.2.0/src/e2a/v1/generated/models/message.py +258 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/message_body_view.py +14 -1
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/message_parsed_view.py +14 -1
- e2a-5.2.0/src/e2a/v1/generated/models/message_summary_view.py +162 -0
- e2a-5.2.0/src/e2a/v1/generated/models/message_view.py +222 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/o_auth_connection_entry.py +14 -1
- e2a-5.2.0/src/e2a/v1/generated/models/page_agent_suppression_view.py +113 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/page_agent_view.py +14 -1
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/page_api_key_view.py +14 -1
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/page_conversation_summary_view.py +14 -1
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/page_domain_view.py +14 -1
- e2a-4.0.1/src/e2a/v1/generated/models/page_event_json.py → e2a-5.2.0/src/e2a/v1/generated/models/page_event_view.py +21 -8
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/page_message_summary_view.py +14 -1
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/page_review_view.py +14 -1
- e2a-5.2.0/src/e2a/v1/generated/models/page_starter_template_view.py +115 -0
- e2a-5.2.0/src/e2a/v1/generated/models/page_suppression_view.py +115 -0
- e2a-5.2.0/src/e2a/v1/generated/models/page_template_summary_view.py +115 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/page_webhook_delivery_view.py +14 -1
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/page_webhook_view.py +14 -1
- e2a-5.2.0/src/e2a/v1/generated/models/payload_too_large_details.py +107 -0
- e2a-5.2.0/src/e2a/v1/generated/models/protection_config_request.py +115 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/protection_config_view.py +14 -1
- e2a-5.2.0/src/e2a/v1/generated/models/protection_direction_request.py +110 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/protection_direction_view.py +14 -1
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/protection_event_export_entry.py +22 -9
- e2a-5.2.0/src/e2a/v1/generated/models/protection_finding_view.py +123 -0
- e2a-4.0.1/src/e2a/v1/generated/models/protection_gate_view.py → e2a-5.2.0/src/e2a/v1/generated/models/protection_gate_request.py +20 -7
- e2a-5.2.0/src/e2a/v1/generated/models/protection_gate_view.py +105 -0
- e2a-5.2.0/src/e2a/v1/generated/models/protection_holds_request.py +105 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/protection_holds_view.py +19 -4
- e2a-5.2.0/src/e2a/v1/generated/models/protection_scan_request.py +100 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/protection_scan_view.py +16 -3
- e2a-5.2.0/src/e2a/v1/generated/models/rate_limited_details.py +100 -0
- e2a-5.2.0/src/e2a/v1/generated/models/rate_limited_envelope.py +104 -0
- e2a-5.2.0/src/e2a/v1/generated/models/rate_limited_error_body.py +110 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/redeliver_delivery.py +14 -1
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/redeliver_event_request.py +14 -1
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/redeliver_view.py +14 -1
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/register_domain_request.py +14 -1
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/reject_request.py +17 -3
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/reject_result_view.py +14 -1
- e2a-4.0.1/src/e2a/v1/generated/models/send_email_request.py → e2a-5.2.0/src/e2a/v1/generated/models/rendered_template_view.py +23 -28
- e2a-5.2.0/src/e2a/v1/generated/models/reply_request.py +129 -0
- e2a-5.2.0/src/e2a/v1/generated/models/retry_after_details.py +101 -0
- e2a-5.2.0/src/e2a/v1/generated/models/review_view.py +146 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/rotate_secret_response.py +14 -1
- e2a-5.2.0/src/e2a/v1/generated/models/send_email_request.py +137 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/send_result_view.py +16 -3
- e2a-5.2.0/src/e2a/v1/generated/models/sending_ramp_view.py +112 -0
- e2a-5.2.0/src/e2a/v1/generated/models/spf_result.py +115 -0
- e2a-5.2.0/src/e2a/v1/generated/models/starter_template_detail_view.py +122 -0
- e2a-5.2.0/src/e2a/v1/generated/models/starter_template_variable_view.py +108 -0
- e2a-5.2.0/src/e2a/v1/generated/models/starter_template_view.py +118 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/suppression_export_entry.py +17 -2
- e2a-4.0.1/src/e2a/v1/generated/models/suppression.py → e2a-5.2.0/src/e2a/v1/generated/models/suppression_view.py +18 -5
- e2a-5.2.0/src/e2a/v1/generated/models/template_part_error.py +102 -0
- e2a-5.2.0/src/e2a/v1/generated/models/template_summary_view.py +111 -0
- e2a-5.2.0/src/e2a/v1/generated/models/template_view.py +119 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/test_webhook_request.py +4 -4
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/test_webhook_response.py +14 -1
- e2a-5.2.0/src/e2a/v1/generated/models/threat_category_view.py +102 -0
- e2a-5.2.0/src/e2a/v1/generated/models/too_many_recipients_details.py +103 -0
- e2a-5.2.0/src/e2a/v1/generated/models/unsubscribe_options.py +98 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/update_agent_request.py +15 -2
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/update_message_request.py +14 -1
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/update_message_result_view.py +14 -1
- e2a-5.2.0/src/e2a/v1/generated/models/update_template_request.py +108 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/update_webhook_request.py +19 -6
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/usage_event_entry.py +20 -7
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/user_export.py +16 -3
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/user_export_user.py +14 -1
- e2a-5.2.0/src/e2a/v1/generated/models/validate_template_request.py +106 -0
- e2a-5.2.0/src/e2a/v1/generated/models/validate_template_response.py +118 -0
- e2a-5.2.0/src/e2a/v1/generated/models/validation_error_details.py +108 -0
- e2a-5.2.0/src/e2a/v1/generated/models/verify_domain_view.py +111 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/webhook_delivery_view.py +18 -5
- e2a-5.2.0/src/e2a/v1/generated/models/webhook_filters_request.py +104 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/webhook_filters_view.py +17 -4
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/models/webhook_view.py +15 -2
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/rest.py +1 -1
- e2a-5.2.0/src/e2a/v1/inbound.py +327 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/pagination.py +16 -0
- e2a-5.2.0/src/e2a/v1/sync_client.py +477 -0
- e2a-5.2.0/src/e2a/v1/webhook_signature.py +446 -0
- e2a-5.2.0/src/e2a/v1/websocket.py +353 -0
- e2a-5.2.0/tests/test_e2e.py +120 -0
- {e2a-4.0.1 → e2a-5.2.0}/tests/test_enum_forward_compat.py +7 -7
- e2a-5.2.0/tests/test_exports.py +163 -0
- e2a-5.2.0/tests/test_v1_client.py +1128 -0
- e2a-5.2.0/tests/test_v1_client_side_validation.py +72 -0
- {e2a-4.0.1 → e2a-5.2.0}/tests/test_v1_errors.py +113 -0
- e2a-5.2.0/tests/test_v1_inbound.py +131 -0
- {e2a-4.0.1 → e2a-5.2.0}/tests/test_v1_pagination.py +34 -0
- {e2a-4.0.1 → e2a-5.2.0}/tests/test_v1_retry.py +95 -3
- e2a-5.2.0/tests/test_v1_sync_client.py +520 -0
- e2a-5.2.0/tests/test_v1_websocket.py +593 -0
- e2a-5.2.0/tests/test_webhook_payloads.py +273 -0
- {e2a-4.0.1 → e2a-5.2.0}/tests/test_webhook_signature.py +72 -4
- e2a-5.2.0/tests/typecheck_event_envelope.py +36 -0
- e2a-5.2.0/tests/typecheck_inbound.py +26 -0
- {e2a-4.0.1 → e2a-5.2.0}/uv.lock +729 -343
- e2a-4.0.1/CHANGELOG.md +0 -190
- e2a-4.0.1/PKG-INFO +0 -179
- e2a-4.0.1/README.md +0 -142
- e2a-4.0.1/scripts/generate-oag.sh +0 -39
- e2a-4.0.1/src/e2a/__init__.py +0 -52
- e2a-4.0.1/src/e2a/v1/__init__.py +0 -71
- e2a-4.0.1/src/e2a/v1/generated/models/__init__.py +0 -96
- e2a-4.0.1/src/e2a/v1/generated/models/agent_view.py +0 -98
- e2a-4.0.1/src/e2a/v1/generated/models/auth_verdict.py +0 -101
- e2a-4.0.1/src/e2a/v1/generated/models/check_result.py +0 -89
- e2a-4.0.1/src/e2a/v1/generated/models/error_body.py +0 -98
- e2a-4.0.1/src/e2a/v1/generated/models/forward_request.py +0 -107
- e2a-4.0.1/src/e2a/v1/generated/models/message.py +0 -208
- e2a-4.0.1/src/e2a/v1/generated/models/message_summary_view.py +0 -134
- e2a-4.0.1/src/e2a/v1/generated/models/message_view.py +0 -160
- e2a-4.0.1/src/e2a/v1/generated/models/page_suppression.py +0 -102
- e2a-4.0.1/src/e2a/v1/generated/models/reply_request.py +0 -107
- e2a-4.0.1/src/e2a/v1/generated/models/result.py +0 -101
- e2a-4.0.1/src/e2a/v1/generated/models/review_view.py +0 -108
- e2a-4.0.1/src/e2a/v1/generated/models/verify_domain_view.py +0 -98
- e2a-4.0.1/src/e2a/v1/webhook_signature.py +0 -141
- e2a-4.0.1/src/e2a/v1/websocket.py +0 -215
- e2a-4.0.1/tests/test_exports.py +0 -84
- e2a-4.0.1/tests/test_v1_client.py +0 -456
- e2a-4.0.1/tests/test_v1_websocket.py +0 -372
- {e2a-4.0.1 → e2a-5.2.0}/scripts/strip-enum-validators.py +0 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/py.typed +0 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/api_response.py +0 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/generated/py.typed +0 -0
- {e2a-4.0.1 → e2a-5.2.0}/src/e2a/v1/py.typed +0 -0
- {e2a-4.0.1 → e2a-5.2.0}/tests/__init__.py +0 -0
- {e2a-4.0.1 → e2a-5.2.0}/tests/test_contract.py +0 -0
|
@@ -25,6 +25,7 @@ cli/dist/
|
|
|
25
25
|
# TypeScript SDK
|
|
26
26
|
sdks/typescript/node_modules/
|
|
27
27
|
sdks/typescript/dist/
|
|
28
|
+
sdks/typescript/.oag-openapi.yaml
|
|
28
29
|
|
|
29
30
|
# Python SDK
|
|
30
31
|
sdks/python/.venv/
|
|
@@ -33,6 +34,8 @@ sdks/python/__pycache__/
|
|
|
33
34
|
sdks/python/src/*.egg-info/
|
|
34
35
|
sdks/python/dist/
|
|
35
36
|
sdks/python/.pytest_cache/
|
|
37
|
+
sdks/python/.oag-openapi.yaml
|
|
38
|
+
sdks/python/.oag-tmp/
|
|
36
39
|
*.pyc
|
|
37
40
|
.gstack/
|
|
38
41
|
.claude/
|
e2a-5.2.0/CHANGELOG.md
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `client.inbound.from_event(event)` returns `AsyncInboundEmail` on
|
|
7
|
+
`AsyncE2AClient` and a blocking `InboundEmail` on `E2AClient`. The facade
|
|
8
|
+
exposes explicit envelope/auth verdicts, reply targets, parsed-body
|
|
9
|
+
truncation, policy flags, bound reply/forward, and lazy attachment `get()`.
|
|
10
|
+
Shared cross-SDK vectors gate async/sync/TypeScript semantics.
|
|
11
|
+
|
|
12
|
+
### Breaking (pre-GA)
|
|
13
|
+
- **Inbound sender and authentication fields now use the final DMARC-aligned
|
|
14
|
+
contract.** Generated message models and raw webhook/WS payloads expose the
|
|
15
|
+
literal RFC 5322 ``header_from``, SMTP ``envelope_from``, nullable
|
|
16
|
+
``verified_domain``, and structured ``authentication`` (``spf``, every
|
|
17
|
+
``dkim`` result, and ``dmarc``). The old aggregate and signed nested-header
|
|
18
|
+
fields are removed. A non-null ``verified_domain`` means DMARC passed for that
|
|
19
|
+
From domain; it does not authenticate the mailbox local part, a person, or
|
|
20
|
+
message content.
|
|
21
|
+
|
|
22
|
+
| Previous 5.x generated property/model | Replacement |
|
|
23
|
+
|---|---|
|
|
24
|
+
| ``from_`` | ``header_from``. Reply routing remains in ``reply_to``. |
|
|
25
|
+
| ``authenticated_from`` | ``verified_domain``, or inspect ``authentication.dmarc.status == "pass"``. |
|
|
26
|
+
| ``auth: AuthVerdict`` | ``authentication: Optional[Authentication]``; ``AuthVerdict`` is removed. |
|
|
27
|
+
| ``CheckResult`` | ``SPFResult``; DKIM and DMARC use the new ``DKIMResult`` and ``DMARCResult`` models. |
|
|
28
|
+
| ``auth_headers`` / ``X-E2A-Auth-*`` | Removed. For webhooks, verify the envelope ``X-E2A-Signature``; REST and WebSocket already use authenticated transports. |
|
|
29
|
+
|
|
30
|
+
- **Implementation-leaked schema names renamed; duplicate schemas collapsed.**
|
|
31
|
+
Generated models: ``EventJSON`` → ``EventView`` (module
|
|
32
|
+
``event_json`` → ``event_view``), ``PageEventJSON`` → ``PageEventView``,
|
|
33
|
+
``Suppression`` → ``SuppressionView``, ``PageSuppression`` →
|
|
34
|
+
``PageSuppressionView``; the duplicate ``Result`` collapsed into the
|
|
35
|
+
existing ``AuthVerdict``, and the duplicate ``AttachmentMeta`` collapsed
|
|
36
|
+
into the canonical ``AttachmentMetaView`` (one attachment-metadata shape for
|
|
37
|
+
REST responses, stable event payloads, and the account export — the
|
|
38
|
+
hand-written webhook payload TypedDict in ``webhook_signature.py`` follows
|
|
39
|
+
the same rename). The wire JSON is unchanged — field names, optionality, and
|
|
40
|
+
values are identical; only the exported type names changed. Migrate:
|
|
41
|
+
``EventJSON`` → ``EventView``, ``Suppression`` → ``SuppressionView``,
|
|
42
|
+
``Result`` → ``AuthVerdict``, ``AttachmentMeta`` → ``AttachmentMetaView``.
|
|
43
|
+
|
|
44
|
+
## 5.2.0
|
|
45
|
+
|
|
46
|
+
### Breaking (pre-GA)
|
|
47
|
+
- **The reserved-word wire field `from` is now uniformly exposed as `from_`
|
|
48
|
+
(PEP 8 trailing underscore) on generated models** (was the generator-mangled
|
|
49
|
+
`var_from`). Affected models: `Message`, `MessageView`, `MessageSummaryView`,
|
|
50
|
+
`ReviewView`, `EmailReceivedData`, `EmailSentData`, `EmailFailedData`
|
|
51
|
+
(generated), plus the generated `list_messages` sender filter parameter. The
|
|
52
|
+
hand-written layer's `messages.list(from_=...)` already used `from_`, so the
|
|
53
|
+
SDK now teaches exactly one spelling; the TypeScript SDK exposes the same
|
|
54
|
+
`from_`. The wire JSON is unchanged — requests and responses still carry
|
|
55
|
+
`from` (pydantic alias). The webhook/WS `data` payload TypedDicts are
|
|
56
|
+
wire-true dicts and keep the literal `"from"` key (access as
|
|
57
|
+
`data["from"]`). Migrate: `message.var_from` → `message.from_`.
|
|
58
|
+
|
|
59
|
+
## 5.1.0
|
|
60
|
+
|
|
61
|
+
### Breaking (pre-GA)
|
|
62
|
+
- **Uniform DELETE responses: every `.delete(...)` now returns a typed deletion
|
|
63
|
+
object instead of `None`.** The API's seven delete endpoints all return
|
|
64
|
+
`200 OK` with `{"deleted": true, <identity key>}` instead of the previous mix
|
|
65
|
+
of `204 No Content` and `200`. New return types: `agents.delete` →
|
|
66
|
+
`DeleteAgentResult` (`deleted`, `email`, `messages_deleted` — the message
|
|
67
|
+
cascade count), `domains.delete` → `DeleteDomainResult` (`domain`),
|
|
68
|
+
`webhooks.delete` → `DeleteWebhookResult` (`id`), `templates.delete` →
|
|
69
|
+
`DeleteTemplateResult` (`id`), `account.api_keys.delete` →
|
|
70
|
+
`DeleteApiKeyResult` (`id`), `account.suppressions.delete` →
|
|
71
|
+
`DeleteSuppressionResult` (`address`); `account.delete()` still returns
|
|
72
|
+
`DeleteUserDataResult`, which now also carries `deleted: true`. `deleted` is
|
|
73
|
+
always `True` — a failed delete raises a typed error, never returns
|
|
74
|
+
`deleted: False`. Applies identically to the sync `E2AClient` facade (it
|
|
75
|
+
mirrors the async surface). Callers that ignored the old `None` return need
|
|
76
|
+
no changes; the SDK still auto-sends the `?confirm=DELETE` guard. Older SDK
|
|
77
|
+
versions whose generated bases expected `204` are incompatible with servers
|
|
78
|
+
running this contract — upgrade together (pre-GA break).
|
|
79
|
+
|
|
80
|
+
## 5.0.0
|
|
81
|
+
|
|
82
|
+
Breaking: the async client class was renamed (the freed name now ships a
|
|
83
|
+
synchronous client), and the WebSocket frame is now the versioned event
|
|
84
|
+
envelope (server #456).
|
|
85
|
+
|
|
86
|
+
### Changed
|
|
87
|
+
- **`E2AClient` → `AsyncE2AClient`.** The 4.x client was async-only, but its
|
|
88
|
+
name inverted the Python-ecosystem convention (httpx, openai, anthropic:
|
|
89
|
+
plain name = sync client, `Async*` prefix = async client) and squatted the
|
|
90
|
+
name the synchronous client needs. The class, exports (`e2a` and `e2a.v1`),
|
|
91
|
+
docs, and examples all now use `AsyncE2AClient`; its behavior is unchanged.
|
|
92
|
+
Migration from 4.x is mechanical: `from e2a.v1 import AsyncE2AClient`.
|
|
93
|
+
- **The WebSocket frame is the versioned event envelope** — the same
|
|
94
|
+
``{type, id, schema_version, created_at, data}`` shape a webhook delivery
|
|
95
|
+
carries, so one parser (and one dedup key: the event ``id``) serves both
|
|
96
|
+
channels. Frames were previously a flat ad-hoc notification object.
|
|
97
|
+
- **`WSNotification` is removed.** ``client.listen(...)`` now yields
|
|
98
|
+
``WSEvent`` envelopes: branch on ``event.type`` (tolerate unknown values —
|
|
99
|
+
forward-compat) and read the payload from ``event.data`` (for
|
|
100
|
+
``email.received`` the flat ``notif.message_id`` / ``notif.delivered_to``
|
|
101
|
+
attributes become ``event.data["message_id"]`` /
|
|
102
|
+
``event.data["delivered_to"]``). The ``is_email_*`` / ``is_domain_*``
|
|
103
|
+
guards narrow the stable payloads.
|
|
104
|
+
|
|
105
|
+
### Added
|
|
106
|
+
- **Typed per-event payload models** for the nine stable event types
|
|
107
|
+
(``EmailReceivedData``, ``EmailSentData``, ``EmailFailedData``,
|
|
108
|
+
``EmailDeliveredData``, ``EmailBouncedData``, ``EmailComplainedData``,
|
|
109
|
+
``DomainSendingVerifiedData``, ``DomainSendingFailedData``,
|
|
110
|
+
``DomainSuppressionAddedData``, plus ``AttachmentMeta``) with narrowing
|
|
111
|
+
guards (``is_email_received``, ``is_email_sent``, …) shared by the webhook
|
|
112
|
+
and WS channels. The shapes are locked to the server's committed golden
|
|
113
|
+
fixtures.
|
|
114
|
+
- ``client.webhooks.fetch_message(event)`` accepts both a verified
|
|
115
|
+
``WebhookEvent`` and a ``WSEvent`` (any envelope-shaped object with
|
|
116
|
+
``type`` and ``data``).
|
|
117
|
+
- **`E2AClient` — the synchronous client** — under the name the rename freed
|
|
118
|
+
(there is deliberately no compatibility alias to the async client). It is a
|
|
119
|
+
facade over `AsyncE2AClient`: a background daemon thread runs an event loop
|
|
120
|
+
for the client's lifetime and every call bridges the corresponding async
|
|
121
|
+
coroutine onto it, so there is exactly one implementation of
|
|
122
|
+
resources/retries/typed errors/pagination and the two surfaces cannot drift.
|
|
123
|
+
- Identical constructor (`api_key`, `base_url`, `max_retries`,
|
|
124
|
+
`max_elapsed_ms`, `timeout_ms`) and resource tree; typed `E2AError`
|
|
125
|
+
subclasses propagate unwrapped, so `except E2ALimitExceededError:` works
|
|
126
|
+
the same as in async code.
|
|
127
|
+
- List endpoints return a **sync pager**: plain `for` iteration, plus
|
|
128
|
+
`page(cursor)` / `to_list(limit=N)` / `for_each(fn)`.
|
|
129
|
+
- `client.listen(address)` returns a plain iterable of `WSEvent` envelopes
|
|
130
|
+
(the same envelope the async `listen()` yields); `close()` from another
|
|
131
|
+
thread unblocks a pending iteration cleanly.
|
|
132
|
+
- Lifecycle: use as a context manager or call `close()` (idempotent). An
|
|
133
|
+
unclosed client is cleaned up at GC/interpreter exit and cannot hang
|
|
134
|
+
shutdown.
|
|
135
|
+
- **Async-context guard:** calling any sync method while an event loop is
|
|
136
|
+
running in the current thread raises a guiding `RuntimeError`
|
|
137
|
+
("use AsyncE2AClient") instead of blocking the loop. 4.x code that still
|
|
138
|
+
imports `E2AClient` now gets the sync client — update those imports to
|
|
139
|
+
`AsyncE2AClient`.
|
|
140
|
+
|
|
141
|
+
## 4.3.0
|
|
142
|
+
|
|
143
|
+
### Breaking (pre-GA)
|
|
144
|
+
- **`AgentIdentity.webhook_healthy` (bool) replaced by `AgentIdentity.webhook_status`
|
|
145
|
+
(optional string enum).** The bool could not distinguish "no webhook
|
|
146
|
+
configured" from "healthy". The new field is an open set — tolerate unknown
|
|
147
|
+
values. Known values: `none` (no webhook matches the agent), `healthy` (an
|
|
148
|
+
enabled matching webhook, no terminally-failed delivery in the last 24h),
|
|
149
|
+
`failing` (an enabled matching webhook had a terminally-failed delivery in
|
|
150
|
+
the last 24h), `disabled` (matching webhooks exist but all are manually
|
|
151
|
+
disabled), `auto_disabled` (all matching webhooks disabled, at least one by
|
|
152
|
+
the chronic-failure sweep). `AgentIdentity` only appears in the account
|
|
153
|
+
export (`account.export()`), so most integrations are unaffected.
|
|
154
|
+
|
|
155
|
+
## 4.2.0
|
|
156
|
+
|
|
157
|
+
Additive, no breaking changes.
|
|
158
|
+
|
|
159
|
+
### Fixed
|
|
160
|
+
- **`templates.list()` / `templates.list_starters()` silently truncated to the
|
|
161
|
+
first page.** Both ignored the server's `next_cursor` and stopped after one
|
|
162
|
+
request, dropping every template/starter past page one. They now thread the
|
|
163
|
+
cursor and auto-page to completion like every other `.list()` (TS SDK
|
|
164
|
+
parity), and accept a `limit=` per-page size.
|
|
165
|
+
|
|
166
|
+
### Added
|
|
167
|
+
- **`AutoPager.page(cursor=None)`** — the manual, caller-driven pagination
|
|
168
|
+
primitive (parity with the TS SDK's `pager.page()`): fetch a SINGLE page and
|
|
169
|
+
get back a `Page` of `items` + `next_cursor`. Pass the previous page's
|
|
170
|
+
`next_cursor` to resume (e.g. checkpoint/restart from a queue); a `None`
|
|
171
|
+
`next_cursor` in the result means there are no more pages. The page size is
|
|
172
|
+
the `limit` baked into the list call that produced the pager.
|
|
173
|
+
|
|
174
|
+
## 4.1.0
|
|
175
|
+
|
|
176
|
+
Additive, no breaking changes.
|
|
177
|
+
|
|
178
|
+
### Added
|
|
179
|
+
- **`E2ALimitExceededError`** — the typed error for a `402 limit_exceeded`
|
|
180
|
+
response (a per-account **quota** cap: monthly messages, storage, agent/domain
|
|
181
|
+
counts). It is **not** retryable. This completes the permanent GA split with
|
|
182
|
+
`E2ARateLimitError` (`429 rate_limited`, a request-**rate**/throughput limit,
|
|
183
|
+
which **is** retryable): branch on the exception type (equivalently the HTTP
|
|
184
|
+
status) — `402` → surface a quota/upgrade path, `429` → back off
|
|
185
|
+
`retry_after_seconds` and retry. A `402` previously surfaced as the base
|
|
186
|
+
`E2AError`; it now surfaces as this subclass (still an `E2AError`, so existing
|
|
187
|
+
`except E2AError` handlers are unaffected).
|
|
188
|
+
- `email.received` is a metadata-only notification; `webhooks.fetch_message(event)`
|
|
189
|
+
+ the `EmailReceivedPayload` type fetch the full message (body + attachments)
|
|
190
|
+
on demand (#321).
|
|
191
|
+
- Per-axis SES sending status surfaced on the domain/sending types (#309).
|
|
192
|
+
- DKIM verification support (#312).
|
|
193
|
+
|
|
194
|
+
## 4.0.0
|
|
195
|
+
|
|
196
|
+
Breaking: the domain DNS-records shape changed (server #304).
|
|
197
|
+
|
|
198
|
+
### Changed
|
|
199
|
+
- **`DomainView.dns_records` is now a single purpose-tagged array
|
|
200
|
+
(`list[DNSRecord]`).** Each record carries `type`, `name`, `value`,
|
|
201
|
+
`priority`, `purpose`, and `status`. Replaces the old
|
|
202
|
+
`dns_records.{ mx, txt, dkim }` object and the separate `sending_dns_records`
|
|
203
|
+
list. Address records by `purpose` (`ownership`, `inbound_mx`, `dkim`,
|
|
204
|
+
`mail_from_mx`, `mail_from_spf`) rather than `dns_records.mx`/`.txt`/`.dkim`.
|
|
205
|
+
The MAIL FROM records live in the same list (returned at registration when the
|
|
206
|
+
sending feature is enabled), and each record now has a per-record `status`
|
|
207
|
+
(`verified`/`pending`/`missing`/`failed`). `purpose` and `status` are open
|
|
208
|
+
sets — tolerate unknown values.
|
|
209
|
+
|
|
210
|
+
## 3.0.0
|
|
211
|
+
|
|
212
|
+
Breaking redesign. The SDK is now a namespaced, **async-only** `E2AClient`
|
|
213
|
+
wrapping a generated client over the agent-scoped `/v1` API surface, with a
|
|
214
|
+
typed error hierarchy, automatic retries + idempotency, and async
|
|
215
|
+
auto-pagination.
|
|
216
|
+
|
|
217
|
+
### Changed
|
|
218
|
+
- **Namespaced, async-only surface.** Resources are grouped under the client:
|
|
219
|
+
`client.agents`, `client.messages`, `client.conversations`, `client.domains`,
|
|
220
|
+
`client.events`, `client.webhooks`, `client.account`. Per-agent methods take
|
|
221
|
+
the agent `address` as the first argument
|
|
222
|
+
(`await client.messages.send(address, {...})`,
|
|
223
|
+
`await client.messages.list(address).to_list(limit=...)`,
|
|
224
|
+
`await client.messages.get(address, id)`,
|
|
225
|
+
`await client.messages.reply(address, id, {...})`). Use the client as an async
|
|
226
|
+
context manager (`async with E2AClient() as client:`).
|
|
227
|
+
- **Webhook verification.** Verify and decode a delivery with the standalone
|
|
228
|
+
`construct_event(raw_body, signature_header, secret)`, which checks the
|
|
229
|
+
`X-E2A-Signature` header and returns a typed event (raising
|
|
230
|
+
`E2AWebhookSignatureError` on a bad signature). Per-webhook `whsec_…` secrets,
|
|
231
|
+
Stripe-style.
|
|
232
|
+
- **Typed errors.** Failures raise `E2AError` subclasses (`E2ANotFoundError`,
|
|
233
|
+
`E2AConflictError`, `E2AValidationError`, `E2ARateLimitError`,
|
|
234
|
+
`E2AWebhookSignatureError`, …) carrying `.code`, `.status`, `.request_id`, and
|
|
235
|
+
`.retryable`.
|
|
236
|
+
|
|
237
|
+
### Removed
|
|
238
|
+
- The flat methods `send` / `reply` / `get_messages` / `get_message` and the
|
|
239
|
+
per-call `agent_email` inference. Pass the agent `address` explicitly.
|
|
240
|
+
- The lower-level `E2AApi` class.
|
|
241
|
+
- The synchronous client — the SDK is async-only.
|
|
242
|
+
- `InboundEmail` / `AsyncInboundEmail` and the `parse_webhook` / `parse` +
|
|
243
|
+
`verify_signature()` flow. Replaced by `construct_event`. There is no
|
|
244
|
+
unverified-email type and no field-access gating.
|
|
245
|
+
|
|
246
|
+
## 2.5.0
|
|
247
|
+
|
|
248
|
+
### Added
|
|
249
|
+
- Generated types for the per-user resource-limits primitive that
|
|
250
|
+
shipped with #158: `LimitsInfo`, `LimitsCaps`, `LimitsUsage`. These
|
|
251
|
+
describe the response shape of `GET /api/v1/users/me/limits`, which
|
|
252
|
+
the hosted dashboard uses to render the upgrade affordance and the
|
|
253
|
+
"you've used X of Y" surface. The high-level `E2AClient` doesn't
|
|
254
|
+
yet expose a typed helper for this endpoint — it's surfaced as a
|
|
255
|
+
dashboard-only concern today, and SDK consumers querying their own
|
|
256
|
+
usage should call `/agents` / `/messages` directly. The types are
|
|
257
|
+
emitted so anyone consuming the raw OpenAPI generation has the
|
|
258
|
+
shapes available.
|
|
259
|
+
|
|
260
|
+
### Notes
|
|
261
|
+
- No runtime client behavior changed in this release. If you're not
|
|
262
|
+
using the limits primitive (self-host deployments without a paid
|
|
263
|
+
tier), 2.5.0 is functionally identical to 2.4.0.
|
|
264
|
+
|
|
265
|
+
## 2.4.0
|
|
266
|
+
|
|
267
|
+
### Added
|
|
268
|
+
- `idempotency_key` parameter on `E2AClient.approve_message()` and its
|
|
269
|
+
async counterpart (and on the lower-level `E2AApi.approve_message()`).
|
|
270
|
+
Approve fires a real SES send, so without a stable key a retry after
|
|
271
|
+
a transient failure could double-send. When supplied it's threaded
|
|
272
|
+
through as the `Idempotency-Key` header; when omitted the SDK mints
|
|
273
|
+
a fresh UUIDv4 per call — that gives network-layer retry safety only.
|
|
274
|
+
Supply a stable key derived from the review event (typically the
|
|
275
|
+
pending `message_id`) to dedupe across an explicit retry loop.
|
|
276
|
+
- `sort`, `from_`, `subject_contains`, `conversation_id`, `since`,
|
|
277
|
+
`until` kwargs on `E2AApi.list_messages()` and the high-level
|
|
278
|
+
`E2AClient.get_messages()` (sync + async). `sort` defaults
|
|
279
|
+
server-side to newest-first; pass `"asc"` for FIFO polling. The
|
|
280
|
+
substring filters are case-insensitive and capped at 200 chars
|
|
281
|
+
server-side. `since` / `until` accept RFC3339 timestamps and
|
|
282
|
+
bracket `created_at`. Filter values are encoded into `next_token`,
|
|
283
|
+
so continuation requests must keep the same filter values.
|
|
284
|
+
|
|
285
|
+
### Changed
|
|
286
|
+
- **Default sort flipped to newest-first** on `GET /messages`. Prior
|
|
287
|
+
releases silently returned oldest-first for `direction=inbound` (the
|
|
288
|
+
SDK default) and newest-first for `direction=all`. A polling agent
|
|
289
|
+
that relied on FIFO drain order should now pass `sort="asc"` to
|
|
290
|
+
preserve the old behavior.
|
|
291
|
+
- `agent_mode` is now a required field on `RegisterAgentRequest`. The
|
|
292
|
+
server previously silently defaulted to `"cloud"` and then 400'd
|
|
293
|
+
with a cryptic "webhook_url is required" message; it now explicitly
|
|
294
|
+
rejects requests missing `agent_mode` with a clear error. Pydantic
|
|
295
|
+
v2 will raise a validation error if you instantiate the request
|
|
296
|
+
without it. Set `agent_mode="local"` or `"cloud"` explicitly.
|
|
297
|
+
|
|
298
|
+
## 2.3.0
|
|
299
|
+
|
|
300
|
+
### Added
|
|
301
|
+
- `idempotency_key` parameter on `E2AClient.send()` / `.reply()` and their async
|
|
302
|
+
counterparts (and on the lower-level `E2AApi.send_email()` /
|
|
303
|
+
`reply_to_message()`). When supplied, it is sent as the `Idempotency-Key`
|
|
304
|
+
header so the server can deduplicate retries of the same send/reply. When
|
|
305
|
+
omitted, the SDK generates a fresh UUIDv4 per call — that gives
|
|
306
|
+
network-layer retry safety only; supply a stable key derived from the
|
|
307
|
+
triggering event (e.g. the inbound message id or a job id) to deduplicate
|
|
308
|
+
across an explicit retry loop.
|
|
309
|
+
- `InboundEmail.reply_to` and `AsyncInboundEmail.reply_to` (`list[str]`) — the
|
|
310
|
+
parsed `Reply-To:` header from the inbound message, surfaced as a first-class
|
|
311
|
+
field so consumers no longer need to re-parse `raw_message` with stdlib
|
|
312
|
+
`email.message_from_bytes()`. Empty list when the header is absent; the SDK
|
|
313
|
+
never silently falls back to `sender`. Use this when the sender is a no-reply
|
|
314
|
+
notifications mailbox (Granola, GitHub, CI bots) and you need the actual
|
|
315
|
+
correspondent.
|
|
316
|
+
- `MessageSummary.reply_to` (`list[str]`) on the REST polling path — the list
|
|
317
|
+
endpoint now mirrors the same field.
|
|
318
|
+
- `reply_to` added to `unverified_payload` for forensic inspection without
|
|
319
|
+
unlocking gated access.
|
|
320
|
+
|
|
321
|
+
### Reply-To trust path (decision)
|
|
322
|
+
`reply_to` is trusted on the same terms as `to`, `cc`, `recipient`,
|
|
323
|
+
`subject`, and the body fields: the e2a server parses it from
|
|
324
|
+
`raw_message`, places it in the JSON envelope, and TLS protects the wire
|
|
325
|
+
to your webhook URL. Treat the field as trustworthy once
|
|
326
|
+
`verify_signature()` succeeds **and** you're confident in your
|
|
327
|
+
relay-to-webhook connection (or via `client.get_message(...)`, which uses
|
|
328
|
+
the authenticated REST channel).
|
|
329
|
+
|
|
330
|
+
**What `verify_signature()` does not prove:** the HMAC binds a fixed set
|
|
331
|
+
of auth headers and `body_hash = SHA-256(raw_message)`. It does not sign
|
|
332
|
+
the JSON envelope itself, and the SDK reads `reply_to`, `to`, `cc`, etc.
|
|
333
|
+
from that envelope rather than re-parsing `raw_message`. So an attacker
|
|
334
|
+
who can modify the JSON wrapping after signing — but cannot modify
|
|
335
|
+
`raw_message` or the signed headers — can rewrite `reply_to` and the
|
|
336
|
+
HMAC will still verify. TLS to your webhook URL is the actual integrity
|
|
337
|
+
layer for the envelope fields; the HMAC is defense-in-depth for proven
|
|
338
|
+
origin and covers the body bytes. If you need byte-exact assurance for a
|
|
339
|
+
specific field, re-parse it from `raw_message` (whose integrity
|
|
340
|
+
`body_hash` *does* cover).
|
|
341
|
+
|
|
342
|
+
**Also not guaranteed:** upstream-DKIM coverage of `Reply-To:`. If the
|
|
343
|
+
original sender's DKIM signature did not sign `Reply-To` (whether
|
|
344
|
+
because they didn't sign it, or there was no DKIM at all), a MITM
|
|
345
|
+
between sender and e2a could have rewritten the header before it reached
|
|
346
|
+
the relay. e2a does not re-verify or surface per-header DKIM coverage
|
|
347
|
+
today — the `Authentication-Results` / SPF/DKIM surface is unchanged.
|
|
348
|
+
For routing decisions where attacker-controlled `Reply-To` would matter,
|
|
349
|
+
also confirm `email.is_verified` and that the sender's domain is one you
|
|
350
|
+
expect.
|
|
351
|
+
|
|
352
|
+
We chose to keep `reply_to` populated whenever it's present (rather than
|
|
353
|
+
masking it on partially-trusted messages or exposing a `reply_to_signed`
|
|
354
|
+
flag) so the field shape stays uniform with `to`/`cc` and consumers can
|
|
355
|
+
make their own policy decision. The trust model is documented on the
|
|
356
|
+
property docstring.
|
|
357
|
+
|
|
358
|
+
### Wire change
|
|
359
|
+
The webhook payload schema now includes an optional `reply_to: string[]`
|
|
360
|
+
field. Existing consumers that ignore unknown fields are unaffected; older
|
|
361
|
+
SDK versions parsing the same payload continue to work and simply do not
|
|
362
|
+
see the new key.
|
|
363
|
+
|
|
364
|
+
### Other generated-type additions
|
|
365
|
+
The high-level surface above is what most consumers will touch. For users
|
|
366
|
+
of `client.api.*` or `e2a.v1.generated.*` directly, the following backend
|
|
367
|
+
endpoints / fields also landed since 2.2.0 and are reflected in the
|
|
368
|
+
regenerated types:
|
|
369
|
+
|
|
370
|
+
- Per-record DNS verification — separate MX / SPF / DKIM diagnostic
|
|
371
|
+
responses on the domain-verification endpoints.
|
|
372
|
+
- Enriched `DashboardAgent` — `Inbound7d`, `Outbound7d`, `Pending`,
|
|
373
|
+
`LastDelivery`, `WebhookHealthy` fields on the dashboard list.
|
|
374
|
+
- OAuth 2.1 authorization-server endpoints (fosite-backed) used by the
|
|
375
|
+
MCP server flow.
|
|
376
|
+
- Per-domain DKIM key generation endpoint.
|
|
377
|
+
- One-time signing-secret reveal on creation.
|
|
378
|
+
- Pending-review polish — provenance, quoted-inbound, headers-preview,
|
|
379
|
+
draft-footer fields on the review payload.
|
|
380
|
+
|
|
381
|
+
These are additive and don't break existing 2.2.0 callers.
|