Skip to content

Commit 7fcdaeb

Browse files
committed
Add support for Email Logs API
1 parent 17c6d15 commit 7fcdaeb

File tree

9 files changed

+758
-0
lines changed

9 files changed

+758
-0
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,9 @@ The same situation applies to both `client.batch_send()` and `client.sending_api
247247
### Suppressions API:
248248
- Suppressions (find & delete) – [`suppressions/suppressions.py`](examples/suppressions/suppressions.py)
249249

250+
### Email Logs API:
251+
- List email logs (with filters & pagination) and get message by ID – [`email_logs/email_logs.py`](examples/email_logs/email_logs.py)
252+
250253
### General API:
251254
- Account Accesses management – [`general/account_accesses.py`](examples/general/account_accesses.py)
252255
- Accounts info – [`general/accounts.py`](examples/general/accounts.py)

examples/email_logs/email_logs.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""Example: List email logs and get a single message by ID."""
2+
3+
from datetime import datetime
4+
from datetime import timedelta
5+
from datetime import timezone
6+
7+
import mailtrap as mt
8+
from mailtrap.models.email_logs import EmailLogsListFilters
9+
from mailtrap.models.email_logs import filter_category_equal
10+
from mailtrap.models.email_logs import filter_empty
11+
from mailtrap.models.email_logs import filter_to_ci_equal
12+
13+
API_TOKEN = "YOUR_API_TOKEN"
14+
ACCOUNT_ID = "YOUR_ACCOUNT_ID"
15+
16+
client = mt.MailtrapClient(token=API_TOKEN, account_id=ACCOUNT_ID)
17+
email_logs_api = client.email_logs_api.email_logs
18+
19+
20+
def list_email_logs():
21+
"""List email logs (first page)."""
22+
return email_logs_api.get_list()
23+
24+
25+
def list_email_logs_with_filters():
26+
"""List email logs from last 2 days, by category(s), with non-empty subject."""
27+
now = datetime.now(timezone.utc)
28+
two_days_ago = now - timedelta(days=2)
29+
filters = EmailLogsListFilters(
30+
sent_after=two_days_ago.isoformat().replace("+00:00", "Z"),
31+
sent_before=now.isoformat().replace("+00:00", "Z"),
32+
subject=filter_empty("not_empty"),
33+
to=filter_to_ci_equal("recipient@example.com"),
34+
category=filter_category_equal(["Welcome Email", "Password Reset"]),
35+
)
36+
return email_logs_api.get_list(filters=filters)
37+
38+
39+
def get_next_page(previous_response):
40+
"""Fetch next page using cursor from previous response."""
41+
if previous_response.next_page_cursor is None:
42+
return None
43+
return email_logs_api.get_list(search_after=previous_response.next_page_cursor)
44+
45+
46+
def get_message(message_id: str):
47+
"""Get a single email log message by UUID."""
48+
return email_logs_api.get_by_id(message_id)
49+
50+
51+
if __name__ == "__main__":
52+
# List first page
53+
response = list_email_logs()
54+
print(f"Total: {response.total_count}, messages: {len(response.messages)}")
55+
for msg in response.messages:
56+
print(f" {msg.message_id} | {msg.from_} -> {msg.to} | {msg.status}")
57+
58+
# Get single message
59+
if response.messages:
60+
detail = get_message(response.messages[0].message_id)
61+
print(f"Detail: {detail.subject}, events: {len(detail.events)}")

mailtrap/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
from .models.contacts import ImportContactParams
1616
from .models.contacts import UpdateContactFieldParams
1717
from .models.contacts import UpdateContactParams
18+
from .models.email_logs import EmailLogMessage
19+
from .models.email_logs import EmailLogsListFilters
20+
from .models.email_logs import EmailLogsListResponse
1821
from .models.inboxes import CreateInboxParams
1922
from .models.inboxes import UpdateInboxParams
2023
from .models.mail import Address

mailtrap/api/email_logs.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from mailtrap.api.resources.email_logs import EmailLogsApi
2+
from mailtrap.http import HttpClient
3+
4+
5+
class EmailLogsBaseApi:
6+
def __init__(self, client: HttpClient, account_id: str) -> None:
7+
self._account_id = account_id
8+
self._client = client
9+
10+
@property
11+
def email_logs(self) -> EmailLogsApi:
12+
return EmailLogsApi(client=self._client, account_id=self._account_id)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
"""Email Logs API resource – list and get email sending logs."""
2+
3+
from typing import Optional
4+
5+
from mailtrap.http import HttpClient
6+
from mailtrap.models.email_logs import EmailLogMessage
7+
from mailtrap.models.email_logs import EmailLogsListFilters
8+
from mailtrap.models.email_logs import EmailLogsListResponse
9+
10+
11+
class EmailLogsApi:
12+
def __init__(self, client: HttpClient, account_id: str) -> None:
13+
self._account_id = account_id
14+
self._client = client
15+
16+
def get_list(
17+
self,
18+
filters: Optional[EmailLogsListFilters] = None,
19+
search_after: Optional[str] = None,
20+
) -> EmailLogsListResponse:
21+
"""
22+
List email logs (paginated). Results are ordered by sent_at descending.
23+
Use search_after with next_page_cursor from the previous response for
24+
the next page.
25+
"""
26+
params: dict[str, object] = {}
27+
if filters is not None:
28+
params.update(filters.to_params())
29+
if search_after is not None:
30+
params["search_after"] = search_after
31+
response = self._client.get(self._api_path(), params=params or None)
32+
messages = [EmailLogMessage.from_api(msg) for msg in response.get("messages", [])]
33+
return EmailLogsListResponse(
34+
messages=messages,
35+
total_count=response.get("total_count", 0),
36+
next_page_cursor=response.get("next_page_cursor"),
37+
)
38+
39+
def get_by_id(self, sending_message_id: str) -> EmailLogMessage:
40+
"""Get a single email log message by its UUID."""
41+
response = self._client.get(self._api_path(sending_message_id))
42+
return EmailLogMessage.from_api(response)
43+
44+
def _api_path(self, sending_message_id: Optional[str] = None) -> str:
45+
path = f"/api/accounts/{self._account_id}/email_logs"
46+
if sending_message_id is not None:
47+
path = f"{path}/{sending_message_id}"
48+
return path

mailtrap/client.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from pydantic import TypeAdapter
77

88
from mailtrap.api.contacts import ContactsBaseApi
9+
from mailtrap.api.email_logs import EmailLogsBaseApi
910
from mailtrap.api.general import GeneralApi
1011
from mailtrap.api.sending import SendingApi
1112
from mailtrap.api.sending_domains import SendingDomainsBaseApi
@@ -102,6 +103,14 @@ def sending_domains_api(self) -> SendingDomainsBaseApi:
102103
client=HttpClient(host=GENERAL_HOST, headers=self.headers),
103104
)
104105

106+
@property
107+
def email_logs_api(self) -> EmailLogsBaseApi:
108+
self._validate_account_id()
109+
return EmailLogsBaseApi(
110+
account_id=cast(str, self.account_id),
111+
client=HttpClient(host=GENERAL_HOST, headers=self.headers),
112+
)
113+
105114
@property
106115
def sending_api(self) -> SendingApi:
107116
http_client = HttpClient(host=self._sending_api_host, headers=self.headers)

0 commit comments

Comments
 (0)