Add Inbound Email API support - #75
Conversation
Pydantic models for the Inbound Email API: folders, inboxes, messages (list/details + attachments), threads (summary/detail/messages), send result, and create/update params. Reply and forward params share a private base and reuse Address/Attachment from the mail models.
Token-scoped resource classes for inbound folders, inboxes, messages (list/get/delete + reply/reply_all/forward), and threads under /api/inbound, grouped by InboundBaseApi and exposed as client.inbound_api. Mirrors the general_api wiring: no account_id.
responses-mocked API tests for inbound folders, inboxes, messages, and threads (happy paths, error tables, token-scoped URLs, flat request bodies, last_id pagination) plus model tests for params serialization, the forward recipient guard, and response parsing.
EmailLogMessage gains rfc_message_id, in_reply_to, references, and thread_id; SendingDomain gains inbound_enabled and inbound_verified. These fields ship with the Inbound Email API.
Runnable example scripts for inbound folders, inboxes, messages (list/get/delete + reply/reply_all/forward), and threads, plus an Inbound Email API entry in the README examples list.
Support inbound_receiving webhooks, which link a webhook to an inbound inbox via inbound_inbox_id. Add the field to Webhook, CreateWebhookParams, and UpdateWebhookParams (webhook_type already accepts any string).
📝 WalkthroughWalkthroughAdded an Inbound Email API with typed models, folder, inbox, message, and thread operations, client integration, runnable examples, README links, and unit tests for serialization, responses, pagination, and errors. ChangesInbound Email API
Sequence Diagram(s)sequenceDiagram
participant MailtrapClient
participant InboundBaseApi
participant InboundMessagesApi
participant HttpClient
MailtrapClient->>InboundBaseApi: access inbound_api
InboundBaseApi->>InboundMessagesApi: create resource API
InboundMessagesApi->>HttpClient: send message request
HttpClient-->>InboundMessagesApi: return API response
InboundMessagesApi-->>MailtrapClient: return typed result
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
mailtrap/api/resources/inbound_messages.py (1)
17-28: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate cursor-pagination and path-building logic between
InboundMessagesApiandInboundThreadsApi.Both classes implement the same
last_idcursor-pagination pattern and the same optional-id path-suffix pattern almost line-for-line. Extract a shared helper to avoid future divergence.
mailtrap/api/resources/inbound_messages.py#L17-L28: replace the inlinelast_idhandling with a shared cursor-params helper (e.g.build_cursor_params(last_id)).mailtrap/api/resources/inbound_messages.py#L67-L71: replace_api_pathwith a shared path-suffix helper that takes the base path and optional id.mailtrap/api/resources/inbound_threads.py#L14-L25: use the same shared cursor-params helper asinbound_messages.py.mailtrap/api/resources/inbound_threads.py#L37-L41: use the same shared path-suffix helper asinbound_messages.py.♻️ Proposed shared helper
# mailtrap/api/resources/_inbound_common.py from typing import Any, Optional def cursor_params(last_id: Optional[str]) -> Optional[dict[str, Any]]: return {"last_id": last_id} if last_id else None def item_path(base_path: str, item_id: Optional[object] = None) -> str: return f"{base_path}/{item_id}" if item_id else base_path- def get_list( - self, inbox_id: int, last_id: Optional[str] = None - ) -> InboundMessagesListResponse: - params: dict[str, Any] = {} - if last_id: - params["last_id"] = last_id - response = self._client.get(self._api_path(inbox_id), params=params or None) + def get_list( + self, inbox_id: int, last_id: Optional[str] = None + ) -> InboundMessagesListResponse: + response = self._client.get( + self._api_path(inbox_id), params=cursor_params(last_id) + ) return InboundMessagesListResponse(**response)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@mailtrap/api/resources/inbound_messages.py` around lines 17 - 28, Extract the duplicated cursor and path-building logic into shared helpers in mailtrap/api/resources/_inbound_common.py, then update InboundMessagesApi.get_list and InboundThreadsApi’s list method at mailtrap/api/resources/inbound_messages.py:17-28 and mailtrap/api/resources/inbound_threads.py:14-25 to use the cursor-params helper, preserving None when last_id is absent. Update InboundMessagesApi._api_path and InboundThreadsApi’s corresponding path construction at mailtrap/api/resources/inbound_messages.py:67-71 and mailtrap/api/resources/inbound_threads.py:37-41 to use the shared base-path/item-id helper, preserving existing optional-ID behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@mailtrap/api/resources/inbound_messages.py`:
- Around line 17-28: Extract the duplicated cursor and path-building logic into
shared helpers in mailtrap/api/resources/_inbound_common.py, then update
InboundMessagesApi.get_list and InboundThreadsApi’s list method at
mailtrap/api/resources/inbound_messages.py:17-28 and
mailtrap/api/resources/inbound_threads.py:14-25 to use the cursor-params helper,
preserving None when last_id is absent. Update InboundMessagesApi._api_path and
InboundThreadsApi’s corresponding path construction at
mailtrap/api/resources/inbound_messages.py:67-71 and
mailtrap/api/resources/inbound_threads.py:37-41 to use the shared
base-path/item-id helper, preserving existing optional-ID behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 892c655d-813a-4c37-86bd-770b9354fb3c
📒 Files selected for processing (25)
README.mdexamples/inbound/folders.pyexamples/inbound/inboxes.pyexamples/inbound/messages.pyexamples/inbound/threads.pymailtrap/__init__.pymailtrap/api/inbound.pymailtrap/api/resources/inbound_folders.pymailtrap/api/resources/inbound_inboxes.pymailtrap/api/resources/inbound_messages.pymailtrap/api/resources/inbound_threads.pymailtrap/client.pymailtrap/models/email_logs.pymailtrap/models/inbound.pymailtrap/models/sending_domains.pymailtrap/models/webhooks.pytests/unit/api/inbound/__init__.pytests/unit/api/inbound/test_inbound_folders.pytests/unit/api/inbound/test_inbound_inboxes.pytests/unit/api/inbound/test_inbound_messages.pytests/unit/api/inbound/test_inbound_threads.pytests/unit/models/test_email_log_models.pytests/unit/models/test_inbound.pytests/unit/models/test_sending_domains.pytests/unit/models/test_webhooks.py
Summary
Adds Inbound Email API support to the Python SDK, matching the released Node and Ruby SDKs. Exposed as
client.inbound_api(token-scoped — noaccount_idrequired, mirroringgeneral_api).client.inbound_api.folders:get_list/get_by_id/create/update/deleteclient.inbound_api.inboxes: folder-scoped CRUD (createtakes optionaldomain_id)client.inbound_api.messages:get_list(cursor vialast_id) /get_by_id/delete+reply/reply_all/forward(send real email)client.inbound_api.threads:get_list/get_by_id/deleteAlso carries the other fields that ship with the Inbound Email API:
EmailLogMessage:rfc_message_id,in_reply_to,references,thread_idSendingDomain:inbound_enabled,inbound_verifiedinbound_inbox_idonWebhook/CreateWebhookParams/UpdateWebhookParams(forinbound_receivingwebhooks)Notes
/api/inbound/...; resource classes take only the HTTP client.reply/reply_all/forwardreuseAddressand the mailAttachmentmodel;ForwardInboundMessageParamsrequires at least onetorecipient (guarded).ReplyInboundMessageParams,ForwardInboundMessageParams).Summary by CodeRabbit