Skip to content

Commit f2f738f

Browse files
authored
Merge pull request #86 from mailtrap/templates-api
Add templates_api for the paginated /api/templates endpoints
2 parents 89f92b4 + c039510 commit f2f738f

11 files changed

Lines changed: 679 additions & 0 deletions

File tree

‎README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ The same situation applies to both `client.batch_send()` and `client.sending_api
239239
- Contact Imports – [`contacts/contact_imports.py`](examples/contacts/contact_imports.py)
240240

241241
### Email Templates API:
242+
- Templates (experimental, `/api/templates`) – [`paginated_templates/templates.py`](examples/paginated_templates/templates.py)
242243
- Templates management – [`email_templates/templates.py`](examples/email_templates/templates.py)
243244

244245
### Sending Domains API:
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import os
2+
from typing import Optional
3+
4+
import mailtrap as mt
5+
from mailtrap.models.common import DeletedObject
6+
from mailtrap.models.paginated_templates import Template
7+
from mailtrap.models.paginated_templates import TemplateListResponse
8+
9+
API_KEY = os.environ["MAILTRAP_API_KEY"]
10+
ACCOUNT_ID = os.environ["MAILTRAP_ACCOUNT_ID"]
11+
12+
client = mt.MailtrapClient(token=API_KEY, account_id=ACCOUNT_ID)
13+
templates_api = client.templates_api.templates
14+
15+
16+
def list_templates() -> TemplateListResponse:
17+
# `token` is the page number (page-token pagination); `per_page` caps at 100.
18+
response = templates_api.get_list(mt.TemplateListParams(per_page=50, token=1))
19+
print(response.data)
20+
print(response.pagination)
21+
return response
22+
23+
24+
def create_template(
25+
name: str,
26+
subject: str,
27+
category: str,
28+
body_html: Optional[str] = None,
29+
body_text: Optional[str] = None,
30+
) -> Template:
31+
params = mt.CreateTemplateParams(
32+
name=name,
33+
subject=subject,
34+
category=category,
35+
body_html=body_html,
36+
body_text=body_text,
37+
)
38+
return templates_api.create(params)
39+
40+
41+
def get_template(template_id: int) -> Template:
42+
return templates_api.get_by_id(template_id)
43+
44+
45+
def update_template(
46+
template_id: int,
47+
name: Optional[str] = None,
48+
subject: Optional[str] = None,
49+
category: Optional[str] = None,
50+
body_html: Optional[str] = None,
51+
body_text: Optional[str] = None,
52+
) -> Template:
53+
params = mt.UpdateTemplateParams(
54+
name=name,
55+
subject=subject,
56+
category=category,
57+
body_html=body_html,
58+
body_text=body_text,
59+
)
60+
return templates_api.update(template_id, params)
61+
62+
63+
def delete_template(template_id: int) -> DeletedObject:
64+
return templates_api.delete(template_id)
65+
66+
67+
if __name__ == "__main__":
68+
list_templates()
69+
70+
created = create_template(
71+
name="Welcome",
72+
subject="Welcome aboard",
73+
category="Onboarding",
74+
body_html="<h1>Hello!</h1>",
75+
)
76+
print(created)
77+
78+
print(get_template(created.id))
79+
print(update_template(created.id, subject="Welcome to Mailtrap"))
80+
print(delete_template(created.id))

‎mailtrap/__init__.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@
5858
from .models.mail import MailFromTemplate
5959
from .models.messages import UpdateEmailMessageParams
6060
from .models.organizations import CreateSubAccountParams
61+
from .models.paginated_templates import CreateTemplateParams
62+
from .models.paginated_templates import Template
63+
from .models.paginated_templates import TemplateListParams
64+
from .models.paginated_templates import TemplateListResponse
65+
from .models.paginated_templates import UpdateTemplateParams
6166
from .models.permissions import PermissionResourceParams
6267
from .models.projects import ProjectParams
6368
from .models.sending_domains import CreateSendingDomainParams
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from mailtrap.api.resources.paginated_templates import PaginatedTemplatesApi
2+
from mailtrap.http import HttpClient
3+
4+
5+
class TemplatesBaseApi:
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 templates(self) -> PaginatedTemplatesApi:
12+
return PaginatedTemplatesApi(account_id=self._account_id, client=self._client)
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
from typing import Optional
2+
3+
from mailtrap.http import HttpClient
4+
from mailtrap.models.common import DeletedObject
5+
from mailtrap.models.paginated_templates import CreateTemplateParams
6+
from mailtrap.models.paginated_templates import Template
7+
from mailtrap.models.paginated_templates import TemplateListParams
8+
from mailtrap.models.paginated_templates import TemplateListResponse
9+
from mailtrap.models.paginated_templates import TemplateResponse
10+
from mailtrap.models.paginated_templates import UpdateTemplateParams
11+
12+
13+
class PaginatedTemplatesApi:
14+
"""
15+
Templates API. The ``/api/templates`` endpoints are experimental: their
16+
request and response shapes may change before general availability.
17+
"""
18+
19+
def __init__(self, client: HttpClient, account_id: str) -> None:
20+
self._account_id = account_id
21+
self._client = client
22+
23+
def get_list(
24+
self, params: Optional[TemplateListParams] = None
25+
) -> TemplateListResponse:
26+
"""
27+
List email templates in the account, one page at a time. Unlike the
28+
``email_templates_api`` list, it does not return every template: pass
29+
``pagination.next_token`` with the same ``per_page`` to get the next
30+
page. Omit ``params`` for the first page with API defaults.
31+
"""
32+
query_params = params.api_query_params if params else None
33+
response = self._client.get(self._api_path(), params=query_params or None)
34+
return TemplateListResponse(**response)
35+
36+
def get_by_id(self, template_id: int) -> Template:
37+
"""Get an email template by ID."""
38+
response = self._client.get(self._api_path(template_id))
39+
return TemplateResponse(**response).data
40+
41+
def create(self, template_params: CreateTemplateParams) -> Template:
42+
"""Create a new email template."""
43+
response = self._client.post(self._api_path(), json=template_params.api_data)
44+
return TemplateResponse(**response).data
45+
46+
def update(self, template_id: int, template_params: UpdateTemplateParams) -> Template:
47+
"""Update an email template. Only the supplied fields are changed."""
48+
response = self._client.patch(
49+
self._api_path(template_id), json=template_params.api_data
50+
)
51+
return TemplateResponse(**response).data
52+
53+
def delete(self, template_id: int) -> DeletedObject:
54+
"""Delete an email template."""
55+
self._client.delete(self._api_path(template_id))
56+
return DeletedObject(template_id)
57+
58+
def _api_path(self, template_id: Optional[int] = None) -> str:
59+
path = f"/api/accounts/{self._account_id}/templates"
60+
if template_id is not None:
61+
return f"{path}/{template_id}"
62+
return path

‎mailtrap/client.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from mailtrap.api.general import GeneralApi
1414
from mailtrap.api.inbound import InboundBaseApi
1515
from mailtrap.api.organizations import OrganizationsBaseApi
16+
from mailtrap.api.paginated_templates import TemplatesBaseApi
1617
from mailtrap.api.resources.stats import StatsApi
1718
from mailtrap.api.sending import SendingApi
1819
from mailtrap.api.sending_domains import SendingDomainsBaseApi
@@ -103,6 +104,14 @@ def email_templates_api(self) -> EmailTemplatesApi:
103104
client=HttpClient(host=GENERAL_HOST, headers=self.headers),
104105
)
105106

107+
@property
108+
def templates_api(self) -> TemplatesBaseApi:
109+
self._validate_account_id("Templates API")
110+
return TemplatesBaseApi(
111+
account_id=cast(str, self.account_id),
112+
client=HttpClient(host=GENERAL_HOST, headers=self.headers),
113+
)
114+
106115
@property
107116
def contacts_api(self) -> ContactsBaseApi:
108117
self._validate_account_id("Contacts API")
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
"""Models for the account-scoped, paginated Templates API (``/api/templates``)."""
2+
3+
from typing import Optional
4+
5+
from pydantic import Field
6+
from pydantic.dataclasses import dataclass
7+
8+
from mailtrap.models.common import Pagination
9+
from mailtrap.models.common import RequestParams
10+
11+
12+
@dataclass
13+
class Template:
14+
"""A single email template."""
15+
16+
id: int
17+
uuid: Optional[str] = None
18+
name: Optional[str] = None
19+
category: Optional[str] = None
20+
subject: Optional[str] = None
21+
body_html: Optional[str] = None
22+
body_text: Optional[str] = None
23+
created_at: Optional[str] = None
24+
updated_at: Optional[str] = None
25+
26+
27+
@dataclass
28+
class TemplateResponse:
29+
"""Envelope of a single-template response."""
30+
31+
data: Template
32+
33+
34+
@dataclass
35+
class TemplateListResponse:
36+
"""Paginated response from listing templates."""
37+
38+
data: list[Template] = Field(default_factory=list)
39+
pagination: Optional[Pagination] = None
40+
41+
42+
@dataclass
43+
class TemplateListParams(RequestParams):
44+
"""
45+
Query params for listing templates. ``token`` is the page number and
46+
``per_page`` is capped at 100.
47+
"""
48+
49+
per_page: Optional[int] = None
50+
token: Optional[int] = None
51+
52+
53+
@dataclass
54+
class CreateTemplateParams(RequestParams):
55+
"""Attributes for creating a template (sent as a flat JSON body)."""
56+
57+
name: str
58+
subject: str
59+
category: str
60+
body_html: Optional[str] = None
61+
body_text: Optional[str] = None
62+
63+
64+
@dataclass
65+
class UpdateTemplateParams(RequestParams):
66+
"""
67+
Attributes for updating a template (sent as a flat JSON body). All fields
68+
are optional, but at least one must be provided.
69+
"""
70+
71+
name: Optional[str] = None
72+
subject: Optional[str] = None
73+
category: Optional[str] = None
74+
body_html: Optional[str] = None
75+
body_text: Optional[str] = None
76+
77+
def __post_init__(self) -> None:
78+
if all(
79+
value is None
80+
for value in [
81+
self.name,
82+
self.subject,
83+
self.category,
84+
self.body_html,
85+
self.body_text,
86+
]
87+
):
88+
raise ValueError("At least one field must be provided for update action")

‎tests/unit/api/paginated_templates/__init__.py‎

Whitespace-only changes.

0 commit comments

Comments
 (0)