Skip to content

MT-22401: Add Email Campaigns API - #74

Draft
Rabsztok wants to merge 2 commits into
mainfrom
MT-22401-python-email-campaigns
Draft

MT-22401: Add Email Campaigns API#74
Rabsztok wants to merge 2 commits into
mainfrom
MT-22401-python-email-campaigns

Conversation

@Rabsztok

@Rabsztok Rabsztok commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Draft until the server-side Email Campaigns API changes are released.

Motivation

MT-22401

Port the Email Campaigns public API (MT-21113) to the Python SDK.

Changes

  • Add 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), and get_stats with an optional date range
  • Models follow the published OpenAPI schema: flat request bodies, data-envelope unwrapping, UUID mailsend_domain_id, audience id lists, request-side TemplateAttributes (subject/body_html/body_text/merge_tags), state-metadata error objects; delete returns DeletedObject
  • Export the campaign models from the package root and add a full-lifecycle example in examples/email_campaigns/

How to test

  • Run examples/email_campaigns/email_campaigns.py with a real API token and a verified sending domain — create a draft, update design/audience, schedule + cancel, fetch stats, delete
  • Verify a lifecycle 422 (e.g. cancel on a draft) surfaces the API error message

Summary by CodeRabbit

  • New Features

    • Added Email Campaigns API support for listing, creating, viewing, updating, scheduling, starting, canceling, terminating, resetting, and deleting campaigns.
    • Added campaign statistics with optional date-range filters.
    • Added structured campaign, template, delivery, scheduling, pagination, and statistics models.
    • Added token-scoped campaign access without requiring an account ID.
    • Added a complete usage example covering the campaign lifecycle.
  • Documentation

    • Updated the supported functionality list with Email Campaigns API examples.

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
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a typed, token-scoped Email Campaigns API with CRUD, scheduling, lifecycle actions, statistics, client exposure, comprehensive unit tests, and a documented workflow example.

Changes

Email Campaigns API

Layer / File(s) Summary
Campaign models and request contracts
mailtrap/models/email_campaigns.py, mailtrap/__init__.py
Defines typed campaign, statistics, pagination, lifecycle metadata, and request parameter models. Re-exports the public types.
Campaign API operations and client wiring
mailtrap/api/resources/email_campaigns.py, mailtrap/api/email_campaigns.py, mailtrap/client.py
Adds token-scoped CRUD, scheduling, lifecycle, and statistics methods. Exposes them through MailtrapClient.email_campaigns_api.
API behavior validation
tests/unit/api/email_campaigns/test_email_campaigns.py, tests/unit/test_client.py
Tests request serialization, response parsing, pagination, errors, lifecycle actions, scheduling, statistics, deletion, and account-independent access.
Campaign workflow example and documentation
examples/email_campaigns/email_campaigns.py, README.md
Adds a sequential campaign workflow and documents the example under supported functionality.

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
Loading

Possibly related PRs

Suggested reviewers: igordobryn, mklocek

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ 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%. 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 identifies the main change: adding the Email Campaigns API.
Description check ✅ Passed The description covers motivation, implementation changes, testing steps, and expected API behavior; the optional Images and GIFs section is not needed.
✨ 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.

@Rabsztok
Rabsztok marked this pull request as ready for review July 30, 2026 12:03

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

Actionable comments posted: 3

🧹 Nitpick comments (3)
tests/unit/api/email_campaigns/test_email_campaigns.py (3)

332-372: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Exact-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: parse responses.calls[0].request.body with json.loads and 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 win

Same 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 win

Same 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

📥 Commits

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

📒 Files selected for processing (10)
  • README.md
  • examples/email_campaigns/email_campaigns.py
  • mailtrap/__init__.py
  • mailtrap/api/email_campaigns.py
  • mailtrap/api/resources/email_campaigns.py
  • mailtrap/client.py
  • mailtrap/models/email_campaigns.py
  • tests/unit/api/email_campaigns/__init__.py
  • tests/unit/api/email_campaigns/test_email_campaigns.py
  • tests/unit/test_client.py

Comment thread examples/email_campaigns/email_campaigns.py
Comment thread mailtrap/client.py
Comment thread mailtrap/models/email_campaigns.py Outdated
@Rabsztok
Rabsztok requested review from IgorDobryn, Ihor-Bilous and mklocek and removed request for Ihor-Bilous July 30, 2026 12:12
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
@Rabsztok

Copy link
Copy Markdown
Contributor Author

Addressed the CodeRabbit review in 069c368:

  • email_campaigns_api no longer requires account_id — the endpoint is token-scoped; the account-id plumbing was removed from the base API and resource, and the client test now asserts the API works token-only
  • Added CreateTemplateAttributes (required subject) for create payloads; TemplateAttributes stays fully optional for partial updates
  • The example now derives the schedule datetime (+1 day) and stats window (last 30 days) at runtime instead of hardcoded dates that went stale
  • The create/update/schedule tests compare parsed JSON bodies instead of exact byte strings

Verified: 494 unit tests pass, mypy ./mailtrap clean, pre-commit hooks pass.

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

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 win

Keep the scheduling test timestamp within the valid window.

ScheduleEmailCampaignParams accepts only a future timestamp within one month. The test in tests/unit/api/email_campaigns/test_email_campaigns.py at Line 545-570 passes 2026-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

📥 Commits

Reviewing files that changed from the base of the PR and between f447e0e and 069c368.

📒 Files selected for processing (8)
  • examples/email_campaigns/email_campaigns.py
  • mailtrap/__init__.py
  • mailtrap/api/email_campaigns.py
  • mailtrap/api/resources/email_campaigns.py
  • mailtrap/client.py
  • mailtrap/models/email_campaigns.py
  • tests/unit/api/email_campaigns/test_email_campaigns.py
  • tests/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

@Rabsztok
Rabsztok marked this pull request as draft July 31, 2026 11:56
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