MT-22401: Add Email Campaigns API - #74
Conversation
Decisions:
- Send flat request bodies (no email_campaign wrapper) per the current OpenAPI contract
- Unwrap the data envelope on single-campaign and stats responses; list keeps {data, pagination}
- delete returns DeletedObject(email_campaign_id) since the API responds 204 No Content
- Add the five lifecycle methods (start/schedule/cancel/terminate/reset) with ScheduleEmailCampaignParams and stats start_date/end_date params
- Require name, mailsend_domain_id (UUID string), from_local_part, and template_attributes on create; split request-side TemplateAttributes from the response-side CampaignTemplate
📝 WalkthroughWalkthroughAdds a typed, token-scoped Email Campaigns API with CRUD, scheduling, lifecycle actions, statistics, client exposure, comprehensive unit tests, and a documented workflow example. ChangesEmail Campaigns API
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant MailtrapClient
participant EmailCampaignsBaseApi
participant EmailCampaignsApi
participant HttpClient
MailtrapClient->>EmailCampaignsBaseApi: create token-scoped campaign API
EmailCampaignsBaseApi->>EmailCampaignsApi: provide HttpClient
EmailCampaignsApi->>HttpClient: send campaign operation request
HttpClient-->>EmailCampaignsApi: return API response
EmailCampaignsApi-->>MailtrapClient: return typed campaign or statistics model
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.
Actionable comments posted: 3
🧹 Nitpick comments (3)
tests/unit/api/email_campaigns/test_email_campaigns.py (3)
332-372: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExact-byte request-body assertions are brittle across create/update/schedule tests. All three sites assert
responses.calls[0].request.body ==a hard-coded byte string, coupling the tests to the JSON serializer's exact key order and escaping rather than the semantic request contract being verified.
tests/unit/api/email_campaigns/test_email_campaigns.py#L332-L372: parseresponses.calls[0].request.bodywithjson.loadsand compare against a dict literal instead of the raw byte string.tests/unit/api/email_campaigns/test_email_campaigns.py#L374-L408: same — compare parsed JSON dict for the "only supplied fields" assertion.tests/unit/api/email_campaigns/test_email_campaigns.py#L538-L564: same — compare parsed JSON dict for the schedule datetime body.🤖 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 `@tests/unit/api/email_campaigns/test_email_campaigns.py` around lines 332 - 372, Replace the exact-byte request-body assertions in tests/unit/api/email_campaigns/test_email_campaigns.py at lines 332-372, 374-408, and 538-564 with json.loads parsing of responses.calls[0].request.body, then compare each parsed payload to a dict literal preserving the existing semantic expectations for create, supplied fields, and schedule datetime bodies.
562-564: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winSame byte-exact body brittleness (schedule request).
Same concern as Lines 332-372 and 401-408; a single-key body is less risky here but still couples to exact serialization.
🤖 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 `@tests/unit/api/email_campaigns/test_email_campaigns.py` around lines 562 - 564, Update the schedule request assertion around responses.calls[0].request.body to parse the JSON body and assert the datetime field value instead of comparing the serialized bytes exactly. Preserve validation that the request contains the expected 2026-06-01T09:00:00.000Z timestamp without coupling the test to JSON formatting or key ordering.
401-408: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winSame byte-exact body brittleness as the create test.
See Lines 332-372 for the shared concern and suggested fix (parse JSON and compare dicts instead of raw bytes).
🤖 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 `@tests/unit/api/email_campaigns/test_email_campaigns.py` around lines 401 - 408, Update the request-body assertion in the relevant email campaign update test to parse responses.calls[0].request.body as JSON and compare the resulting dictionary structure, matching the approach used by the create test around lines 332-372 instead of asserting exact byte serialization.
🤖 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.
Inline comments:
In `@examples/email_campaigns/email_campaigns.py`:
- Around line 69-76: Update schedule_email_campaign to derive a future
scheduling datetime at runtime instead of using the stale hardcoded June 2026
value. Also update the example’s stats date range to align with the newly
scheduled campaign date so the campaign results remain visible.
In `@mailtrap/client.py`:
- Around line 121-127: Update the email_campaigns_api property to remove the
_validate_account_id requirement and stop passing account_id into
EmailCampaignsBaseApi, allowing token-scoped clients without an account ID.
Adjust the related account-ID validation test to assert the API remains usable
without account_id.
In `@mailtrap/models/email_campaigns.py`:
- Line 189: Update CreateEmailCampaignParams so template_attributes uses a
create-specific template model requiring subject: str, or validates that subject
is present before submission. Keep the existing partial TemplateAttributes model
for update payloads and ensure create() cannot send a template without a
subject.
---
Nitpick comments:
In `@tests/unit/api/email_campaigns/test_email_campaigns.py`:
- Around line 332-372: Replace the exact-byte request-body assertions in
tests/unit/api/email_campaigns/test_email_campaigns.py at lines 332-372,
374-408, and 538-564 with json.loads parsing of responses.calls[0].request.body,
then compare each parsed payload to a dict literal preserving the existing
semantic expectations for create, supplied fields, and schedule datetime bodies.
- Around line 562-564: Update the schedule request assertion around
responses.calls[0].request.body to parse the JSON body and assert the datetime
field value instead of comparing the serialized bytes exactly. Preserve
validation that the request contains the expected 2026-06-01T09:00:00.000Z
timestamp without coupling the test to JSON formatting or key ordering.
- Around line 401-408: Update the request-body assertion in the relevant email
campaign update test to parse responses.calls[0].request.body as JSON and
compare the resulting dictionary structure, matching the approach used by the
create test around lines 332-372 instead of asserting exact byte serialization.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 644203b8-ae5c-4930-a576-f37c3557d75a
📒 Files selected for processing (10)
README.mdexamples/email_campaigns/email_campaigns.pymailtrap/__init__.pymailtrap/api/email_campaigns.pymailtrap/api/resources/email_campaigns.pymailtrap/client.pymailtrap/models/email_campaigns.pytests/unit/api/email_campaigns/__init__.pytests/unit/api/email_campaigns/test_email_campaigns.pytests/unit/test_client.py
Decisions: - Drop the account_id requirement from email_campaigns_api — the endpoint is token-scoped and the account is resolved server-side from the token - Add CreateTemplateAttributes with a required subject so create() cannot send a template the API would reject; TemplateAttributes stays partial for updates - Derive the example's schedule datetime and stats window at runtime instead of hardcoded dates that go stale - Compare request bodies as parsed JSON in tests instead of exact bytes
|
Addressed the CodeRabbit review in 069c368:
Verified: 494 unit tests pass, |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
mailtrap/models/email_campaigns.py (1)
230-237: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winKeep the scheduling test timestamp within the valid window.
ScheduleEmailCampaignParamsaccepts only a future timestamp within one month. The test intests/unit/api/email_campaigns/test_email_campaigns.pyat Line 545-570 passes2026-06-01T09:00:00.000Z. On July 31, 2026, this timestamp is in the past. Model construction fails before the mocked request runs.Freeze the clock in the test and derive a timestamp inside the allowed window.
🤖 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/models/email_campaigns.py` around lines 230 - 237, Update the scheduling test around the ScheduleEmailCampaignParams construction to freeze time, then derive the datetime value from the frozen current time so it remains future and within the one-month limit. Replace the hard-coded timestamp while preserving the existing mocked request assertions.
🤖 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.
Outside diff comments:
In `@mailtrap/models/email_campaigns.py`:
- Around line 230-237: Update the scheduling test around the
ScheduleEmailCampaignParams construction to freeze time, then derive the
datetime value from the frozen current time so it remains future and within the
one-month limit. Replace the hard-coded timestamp while preserving the existing
mocked request assertions.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 83b89192-e938-45a6-b2d4-d3c8c8361994
📒 Files selected for processing (8)
examples/email_campaigns/email_campaigns.pymailtrap/__init__.pymailtrap/api/email_campaigns.pymailtrap/api/resources/email_campaigns.pymailtrap/client.pymailtrap/models/email_campaigns.pytests/unit/api/email_campaigns/test_email_campaigns.pytests/unit/test_client.py
🚧 Files skipped from review as they are similar to previous changes (2)
- mailtrap/init.py
- tests/unit/api/email_campaigns/test_email_campaigns.py
Motivation
MT-22401
Port the Email Campaigns public API (MT-21113) to the Python SDK.
Changes
EmailCampaignsApi(client.email_campaigns_api.email_campaigns) covering the full contract: list (page-token pagination + search), get, create, update, delete, the five lifecycle actions (start,schedule,cancel,terminate,reset), andget_statswith an optional date rangedata-envelope unwrapping, UUIDmailsend_domain_id, audience id lists, request-sideTemplateAttributes(subject/body_html/body_text/merge_tags), state-metadata error objects;deletereturnsDeletedObjectexamples/email_campaigns/How to test
examples/email_campaigns/email_campaigns.pywith a real API token and a verified sending domain — create a draft, update design/audience, schedule + cancel, fetch stats, deletecancelon a draft) surfaces the API error messageSummary by CodeRabbit
New Features
Documentation