Skip to content

Conversation

Ihor-Bilous
Copy link
Collaborator

@Ihor-Bilous Ihor-Bilous commented Sep 14, 2025

Motivation

In this PR I added MessagesApi, related models, tests, examples

Changes

  • Added MessagesApi, models, test, examples

How to test

  • see examples/testing/messages.py

Summary by CodeRabbit

  • New Features
    • Added Testing Messages API to manage messages (list, view, mark read, delete, forward), retrieve bodies (text, HTML, raw, EML, source), headers, spam report, and HTML analysis.
    • Exposed UpdateEmailMessageParams in the top-level package.
  • Bug Fixes
    • More robust HTTP handling: returns raw text for non‑JSON success responses and clearer errors for empty/invalid JSON bodies.
  • Documentation
    • New example demonstrating message operations.
  • Tests
    • Comprehensive unit tests for the Messages API.

Copy link

coderabbitai bot commented Sep 14, 2025

Walkthrough

Adds a MessagesApi resource and models for message operations, integrates it into TestingApi, exposes UpdateEmailMessageParams from the package root, updates HttpClient JSON/empty-body handling, supplies an examples script for message helpers, and adds comprehensive unit tests for all message endpoints.

Changes

Cohort / File(s) Summary
Examples: message helpers
examples/testing/messages.py
New example wrapping Testing API messages endpoints: CRUD, list, forward, reports, bodies, headers; includes placeholder config and simple demo.
Public exports
mailtrap/__init__.py
Exposes UpdateEmailMessageParams from mailtrap.models.messages.
Messages API resource
mailtrap/api/resources/messages.py
New MessagesApi with methods: show, update, delete, list, forward, spam report, HTML analysis, multiple body fetches, headers; builds paths and maps responses to models.
Testing API integration
mailtrap/api/testing.py
Adds messages property returning configured MessagesApi.
HTTP client handling
mailtrap/http.py
On OK responses, return JSON or raw text if not JSON; on failures, handle empty bodies (404 “Not Found”, others “Empty response body”); normalize invalid JSON errors.
Message models
mailtrap/models/messages.py
Adds enums and dataclasses for messages, spam/analysis reports, SMTP info; UpdateEmailMessageParams formats is_read for API.
Unit tests
tests/unit/api/testing/test_messages.py
Comprehensive tests for all MessagesApi endpoints, parameters, content types, and error handling using stubbed HTTP responses.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Dev as Developer Code
  participant Client as MailtrapClient
  participant Testing as TestingApi
  participant Messages as MessagesApi
  participant HTTP as HttpClient
  participant API as Mailtrap Testing API

  Dev->>Client: client.testing_api.messages.get_list(inbox_id, search, last_id, page)
  Client->>Testing: testing_api
  Testing->>Messages: messages (account_id, client)
  Messages->>HTTP: GET /api/accounts/{account}/inboxes/{inbox}/messages{?q,last_id,page}
  HTTP->>API: Request
  API-->>HTTP: 200 OK (JSON list)
  HTTP-->>Messages: Parsed list[EmailMessage]
  Messages-->>Dev: list[EmailMessage]

  note right of HTTP: On non-JSON 200 OK → return text
  note right of HTTP: On failed empty body → APIError ("Not Found"/"Empty response body")
Loading
sequenceDiagram
  autonumber
  actor Dev as Developer Code
  participant Messages as MessagesApi
  participant HTTP as HttpClient
  participant API as Mailtrap Testing API

  Dev->>Messages: update(inbox_id, message_id, UpdateEmailMessageParams)
  Messages->>HTTP: PATCH /.../messages/{message_id} body: {"is_read":"true|false"}
  HTTP->>API: Request
  API-->>HTTP: 200 OK (EmailMessage JSON)
  HTTP-->>Messages: EmailMessage
  Messages-->>Dev: EmailMessage
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • IgorDobryn
  • i7an
  • VladimirTaytor
  • andrii-porokhnavets

Poem

I twitch my nose and tap the key,
New messages hop straight to me.
Headers checked and spam in sight,
I forward, mark, and set them right.
Tests snug in burrow, all things clear—hoppity cheer! 🐇📬

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title clearly summarizes the primary change by stating that MessagesApi, related models, examples, and tests were added and it references the issue number, so it is directly related to the changeset and concise enough for a reviewer to understand the main intent.
Description Check ✅ Passed The pull request description includes the required template sections "Motivation", "Changes", and "How to test", so it matches the repository template structure and conveys the intent and high-level changes; however the "How to test" section is minimal and does not include step‑by‑step commands or the checklist items present in the template.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ISSUE-26

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6917ff2 and c75cb42.

📒 Files selected for processing (1)
  • mailtrap/http.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • mailtrap/http.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test python3.9 on windows-latest
  • GitHub Check: Test python3.13 on windows-latest
  • GitHub Check: Test python3.12 on windows-latest
  • GitHub Check: Test python3.11 on windows-latest
  • GitHub Check: Test python3.10 on windows-latest

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


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 and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (6)
mailtrap/http.py (1)

66-70: Treat whitespace-only bodies as empty

Some servers return a single newline/space on errors; consider stripping before the emptiness check to avoid misleading “Invalid JSON”.

-        if not response.content:
+        if not response.content or not response.content.strip():
             if status_code == 404:
                 raise APIError(status_code, errors=["Not Found"])
             raise APIError(status_code, errors=["Empty response body"])
tests/unit/api/testing/test_messages.py (2)

17-17: Unused constant

ATTACHMENT_ID isn’t referenced.

-ATTACHMENT_ID = 67

190-207: Doc vs behavior mismatch for pagination

Your MessagesApi docstring says last_id overrides page, but tests assert both are sent. Decide one: either (a) enforce override in code and adjust this test, or (b) update the docstring.

I can provide a patch for (a) in MessagesApi if you choose that route.

examples/testing/messages.py (2)

9-12: Placeholders: tweak INBOX_ID type; suppress S105 for token

  • Use an int for INBOX_ID to match function signatures.
  • Keep placeholder token but silence the linter as this is an example.
-API_TOKEN = "YOU_API_TOKEN"
+API_TOKEN = "YOU_API_TOKEN"  # noqa: S105 (example placeholder)
 ACCOUNT_ID = "YOU_ACCOUNT_ID"
-INBOX_ID = "YOUR_INBOX_ID"
+INBOX_ID = 123456  # example inbox id

48-77: Fix type hints: message_id should be int; headers return type is dict

Align wrappers with MessagesApi signatures and actual return types.

-from mailtrap.models.messages import AnalysisReport
+from typing import Any
+from mailtrap.models.messages import AnalysisReport
@@
-def get_spam_report(inbox_id: int, message_id: str) -> SpamReport:
+def get_spam_report(inbox_id: int, message_id: int) -> SpamReport:
@@
-def get_html_analysis(inbox_id: int, message_id: str) -> AnalysisReport:
+def get_html_analysis(inbox_id: int, message_id: int) -> AnalysisReport:
@@
-def get_text_body(inbox_id: int, message_id: str) -> str:
+def get_text_body(inbox_id: int, message_id: int) -> str:
@@
-def get_raw_body(inbox_id: int, message_id: str) -> str:
+def get_raw_body(inbox_id: int, message_id: int) -> str:
@@
-def get_html_source(inbox_id: int, message_id: str) -> str:
+def get_html_source(inbox_id: int, message_id: int) -> str:
@@
-def get_html_body(inbox_id: int, message_id: str) -> str:
+def get_html_body(inbox_id: int, message_id: int) -> str:
@@
-def get_eml_body(inbox_id: int, message_id: str) -> str:
+def get_eml_body(inbox_id: int, message_id: int) -> str:
@@
-def get_mail_headers(inbox_id: int, message_id: str) -> str:
-    return messages_api.get_mail_headers(inbox_id=inbox_id, message_id=message_id)
+def get_mail_headers(inbox_id: int, message_id: int) -> dict[str, Any]:
+    return messages_api.get_mail_headers(inbox_id=inbox_id, message_id=message_id)
mailtrap/api/resources/messages.py (1)

148-152: Avoid falsy‑id pitfall

Use an explicit None check so id=0 (if ever valid) isn’t dropped.

-        if message_id:
+        if message_id is not None:
             return f"{path}/{message_id}"
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 22b1e1d and 6917ff2.

📒 Files selected for processing (7)
  • examples/testing/messages.py (1 hunks)
  • mailtrap/__init__.py (1 hunks)
  • mailtrap/api/resources/messages.py (1 hunks)
  • mailtrap/api/testing.py (2 hunks)
  • mailtrap/http.py (2 hunks)
  • mailtrap/models/messages.py (1 hunks)
  • tests/unit/api/testing/test_messages.py (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-09-04T19:31:01.169Z
Learnt from: Ihor-Bilous
PR: railsware/mailtrap-python#39
File: examples/suppressions.py:1-10
Timestamp: 2025-09-04T19:31:01.169Z
Learning: In the mailtrap-python repository, all example files consistently use placeholder strings like `API_TOKEN = "YOU_API_TOKEN"` and `ACCOUNT_ID = "YOU_ACCOUNT_ID"` instead of environment variable lookups. This pattern should be maintained for consistency across examples.

Applied to files:

  • examples/testing/messages.py
📚 Learning: 2025-09-05T23:31:55.179Z
Learnt from: Ihor-Bilous
PR: railsware/mailtrap-python#40
File: mailtrap/api/resources/inboxes.py:40-67
Timestamp: 2025-09-05T23:31:55.179Z
Learning: The Mailtrap API does not return 204 No Content responses for inbox management endpoints (delete, clean, mark_as_read, reset_credentials, enable_email_address, reset_email_username). All these endpoints return JSON data that can be used to construct Inbox objects.

Applied to files:

  • examples/testing/messages.py
📚 Learning: 2025-08-12T23:07:25.653Z
Learnt from: Ihor-Bilous
PR: railsware/mailtrap-python#31
File: mailtrap/config.py:1-1
Timestamp: 2025-08-12T23:07:25.653Z
Learning: In Mailtrap's API architecture, Testing API resources (Projects, Inboxes, etc.) use the main "mailtrap.io" host, while only email sending functionality uses "sandbox.api.mailtrap.io" as the host.

Applied to files:

  • mailtrap/api/testing.py
🧬 Code graph analysis (7)
examples/testing/messages.py (4)
mailtrap/api/testing.py (1)
  • messages (26-27)
mailtrap/models/messages.py (5)
  • AnalysisReport (126-127)
  • EmailMessage (47-69)
  • ForwardedMessage (84-85)
  • SpamReport (96-103)
  • UpdateEmailMessageParams (73-80)
mailtrap/client.py (1)
  • testing_api (52-58)
mailtrap/api/resources/messages.py (13)
  • show_message (19-22)
  • update (24-35)
  • delete (37-40)
  • get_list (42-88)
  • forward (90-98)
  • get_spam_report (100-103)
  • get_html_analysis (105-108)
  • get_text_body (110-114)
  • get_raw_body (116-120)
  • get_html_source (122-127)
  • get_html_body (129-133)
  • get_eml_body (135-139)
  • get_mail_headers (141-146)
tests/unit/api/testing/test_messages.py (4)
mailtrap/api/resources/messages.py (14)
  • MessagesApi (14-152)
  • get_list (42-88)
  • show_message (19-22)
  • update (24-35)
  • delete (37-40)
  • forward (90-98)
  • get_spam_report (100-103)
  • get_html_analysis (105-108)
  • get_text_body (110-114)
  • get_html_body (129-133)
  • get_raw_body (116-120)
  • get_html_source (122-127)
  • get_eml_body (135-139)
  • get_mail_headers (141-146)
mailtrap/exceptions.py (1)
  • APIError (10-15)
mailtrap/http.py (5)
  • HttpClient (14-106)
  • get (26-30)
  • patch (40-42)
  • delete (44-46)
  • post (32-34)
mailtrap/models/messages.py (2)
  • EmailMessage (47-69)
  • UpdateEmailMessageParams (73-80)
mailtrap/api/testing.py (2)
mailtrap/api/resources/messages.py (1)
  • MessagesApi (14-152)
tests/unit/api/testing/test_messages.py (1)
  • client (24-25)
mailtrap/__init__.py (2)
mailtrap/api/testing.py (1)
  • messages (26-27)
mailtrap/models/messages.py (1)
  • UpdateEmailMessageParams (73-80)
mailtrap/api/resources/messages.py (3)
mailtrap/http.py (5)
  • HttpClient (14-106)
  • get (26-30)
  • patch (40-42)
  • delete (44-46)
  • post (32-34)
mailtrap/api/testing.py (1)
  • messages (26-27)
mailtrap/models/messages.py (7)
  • AnalysisReport (126-127)
  • AnalysisReportResponse (141-142)
  • EmailMessage (47-69)
  • ForwardedMessage (84-85)
  • SpamReport (96-103)
  • UpdateEmailMessageParams (73-80)
  • api_data (77-80)
mailtrap/http.py (1)
mailtrap/exceptions.py (1)
  • APIError (10-15)
mailtrap/models/messages.py (1)
mailtrap/models/common.py (1)
  • RequestParams (13-19)
🪛 Ruff (0.12.2)
examples/testing/messages.py

9-9: Possible hardcoded password assigned to: "API_TOKEN"

(S105)

🔇 Additional comments (9)
mailtrap/http.py (2)

58-61: OK to fall back to raw text on non‑JSON success

Returning response.text on JSON parse errors for 2xx is reasonable for the body endpoints.

Please confirm this matches Mailtrap’s successful non‑JSON endpoints (text/html, message/rfc822).


71-81: Error mapping looks good

401 → AuthorizationError and generic APIError with flattened messages is appropriate.

mailtrap/models/messages.py (3)

95-104: Alias-based parsing into dataclasses: confirm Pydantic behavior

You’re instantiating SpamReport/SpamDetail with aliased keys (e.g., “ResponseCode”, “Pts”). With pydantic.dataclasses, alias handling on init depends on v2 behavior. If aliases aren’t honored at init, switch these to BaseModel or parse via TypeAdapter before constructing.

Would you like me to provide a drop-in BaseModel variant if verification fails?


72-81: Lowercasing bools for API payloads

Converting booleans to "true"/"false" strings matches the documented API; impl is fine.


46-70: EmailMessage shape looks consistent with tests

Field types and nesting align with responses used in tests.

mailtrap/api/testing.py (1)

25-27: Good addition — resource wiring mirrors existing pattern

Exposes MessagesApi via the testing surface with the correct host/client.

mailtrap/__init__.py (1)

21-21: Re‑export of UpdateEmailMessageParams

Public API ergonomics improved; matches example usage.

tests/unit/api/testing/test_messages.py (1)

550-571: Mixed content-types in error responses are handled

Good coverage for text/plain and empty bodies; aligns with HttpClient changes.

mailtrap/api/resources/messages.py (1)

42-78: Docstring is clear and helpful

Nice endpoint notes and parameter docs.

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