Skip to content

Add Inbound Email API support - #75

Open
mklocek wants to merge 6 commits into
mainfrom
inbound-v2
Open

Add Inbound Email API support#75
mklocek wants to merge 6 commits into
mainfrom
inbound-v2

Conversation

@mklocek

@mklocek mklocek commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds Inbound Email API support to the Python SDK, matching the released Node and Ruby SDKs. Exposed as client.inbound_api (token-scoped — no account_id required, mirroring general_api).

  • Foldersclient.inbound_api.folders: get_list / get_by_id / create / update / delete
  • Inboxesclient.inbound_api.inboxes: folder-scoped CRUD (create takes optional domain_id)
  • Messagesclient.inbound_api.messages: get_list (cursor via last_id) / get_by_id / delete + reply / reply_all / forward (send real email)
  • Threadsclient.inbound_api.threads: get_list / get_by_id / delete

Also carries the other fields that ship with the Inbound Email API:

  • EmailLogMessage: rfc_message_id, in_reply_to, references, thread_id
  • SendingDomain: inbound_enabled, inbound_verified
  • Webhooks: inbound_inbox_id on Webhook / CreateWebhookParams / UpdateWebhookParams (for inbound_receiving webhooks)

Notes

  • Endpoints are token-scoped under /api/inbound/...; resource classes take only the HTTP client.
  • Request bodies are sent flat (the v2 inbound API does not wrap params under a resource key).
  • reply / reply_all / forward reuse Address and the mail Attachment model; ForwardInboundMessageParams requires at least one to recipient (guarded).
  • Reply/forward params share a private base (ReplyInboundMessageParams, ForwardInboundMessageParams).

Summary by CodeRabbit

  • New Features
    • Added Inbound Email API support for managing folders, inboxes, messages, and threads.
    • Added capabilities to retrieve, create, update, and delete inbound resources.
    • Added message replies, reply-all, forwarding, and pagination support.
    • Added inbound email models, attachment handling, threading details, and webhook integration.
  • Documentation
    • Added Inbound Email API usage examples and README guidance.
  • Tests
    • Added comprehensive coverage for inbound APIs, models, pagination, validation, and error handling.

mklocek added 6 commits July 31, 2026 11:09
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).
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Added 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.

Changes

Inbound Email API

Layer / File(s) Summary
Inbound data contracts
mailtrap/models/inbound.py, mailtrap/models/email_logs.py, mailtrap/models/sending_domains.py, mailtrap/models/webhooks.py, mailtrap/__init__.py, tests/unit/models/*
Added inbound models, request parameters, message threading fields, webhook and sending-domain fields, public exports, and model tests.
Inbound resource operations
mailtrap/api/resources/inbound_*.py, tests/unit/api/inbound/*
Added folder, inbox, message, and thread HTTP operations with typed responses, pagination, message actions, deletion results, and API error tests.
Client inbound access
mailtrap/api/inbound.py, mailtrap/client.py
Added InboundBaseApi and exposed it through MailtrapClient.inbound_api.
Examples and documentation
examples/inbound/*, README.md
Added runnable inbound API examples and README links for each resource.
Estimated code review effort: 4 (Complex) ~45 minutes

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
Loading

Possibly related PRs

Suggested reviewers: vladimirtaytor, andrii-porokhnavets

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 24.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely summarizes the primary change: adding Inbound Email API support.
Description check ✅ Passed The description gives a detailed change summary and implementation notes, but it omits the template's testing and images sections.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
mailtrap/api/resources/inbound_messages.py (1)

17-28: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate cursor-pagination and path-building logic between InboundMessagesApi and InboundThreadsApi.

Both classes implement the same last_id cursor-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 inline last_id handling with a shared cursor-params helper (e.g. build_cursor_params(last_id)).
  • mailtrap/api/resources/inbound_messages.py#L67-L71: replace _api_path with 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 as inbound_messages.py.
  • mailtrap/api/resources/inbound_threads.py#L37-L41: use the same shared path-suffix helper as inbound_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

📥 Commits

Reviewing files that changed from the base of the PR and between cd63644 and 9439b16.

📒 Files selected for processing (25)
  • README.md
  • examples/inbound/folders.py
  • examples/inbound/inboxes.py
  • examples/inbound/messages.py
  • examples/inbound/threads.py
  • mailtrap/__init__.py
  • mailtrap/api/inbound.py
  • mailtrap/api/resources/inbound_folders.py
  • mailtrap/api/resources/inbound_inboxes.py
  • mailtrap/api/resources/inbound_messages.py
  • mailtrap/api/resources/inbound_threads.py
  • mailtrap/client.py
  • mailtrap/models/email_logs.py
  • mailtrap/models/inbound.py
  • mailtrap/models/sending_domains.py
  • mailtrap/models/webhooks.py
  • tests/unit/api/inbound/__init__.py
  • tests/unit/api/inbound/test_inbound_folders.py
  • tests/unit/api/inbound/test_inbound_inboxes.py
  • tests/unit/api/inbound/test_inbound_messages.py
  • tests/unit/api/inbound/test_inbound_threads.py
  • tests/unit/models/test_email_log_models.py
  • tests/unit/models/test_inbound.py
  • tests/unit/models/test_sending_domains.py
  • tests/unit/models/test_webhooks.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant