Skip to content

Commit b8898f5

Browse files
committed
Improve template management docs
1 parent 709f096 commit b8898f5

File tree

5 files changed

+170
-1
lines changed

5 files changed

+170
-1
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
## [2.2.0] - 2025-05-20
2+
Add Email Templates API support in MailtrapClient
3+
14
## [2.1.0] - 2025-05-12
25
- Add sandbox mode support in MailtrapClient
36
- It requires inbox_id parameter to be set

README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,47 @@ client = mt.MailtrapClient(token="your-api-key")
122122
client.send(mail)
123123
```
124124

125+
### Managing templates
126+
127+
Mailtrap provides a dedicated API to manage reusable email templates. The
128+
client exposes several helper methods which all expect your account identifier
129+
as the first argument:
130+
131+
- `email_templates(account_id)` – list all templates
132+
- `create_email_template(account_id, data)` – create a new template
133+
- `update_email_template(account_id, template_id, data)` – update a template
134+
- `delete_email_template(account_id, template_id)` – remove a template
135+
136+
#### Creating a template
137+
138+
The `data` dictionary **must** contain `name`, `subject` and `category`. You can
139+
optionally provide `body_html` and/or `body_text` to store the template content.
140+
141+
```python
142+
import mailtrap as mt
143+
144+
client = mt.MailtrapClient(token="your-api-key")
145+
146+
# list templates
147+
templates = client.email_templates(1)
148+
149+
template_data = {
150+
"name": "Welcome",
151+
"subject": "Welcome on board",
152+
"category": "Promotion",
153+
"body_html": "<h1>Hello {{user_name}}</h1>",
154+
"body_text": "Hello {{user_name}}",
155+
}
156+
157+
created = client.create_email_template(1, template_data)
158+
159+
# update template
160+
client.update_email_template(1, created["id"], {"subject": "New subject"})
161+
162+
# delete template
163+
client.delete_email_template(1, created["id"])
164+
```
165+
125166
## Contributing
126167

127168
Bug reports and pull requests are welcome on [GitHub](https://github.com/railsware/mailtrap-python). This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](CODE_OF_CONDUCT.md).

mailtrap/client.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from typing import Any
12
from typing import NoReturn
23
from typing import Optional
34
from typing import Union
@@ -15,19 +16,22 @@ class MailtrapClient:
1516
DEFAULT_PORT = 443
1617
BULK_HOST = "bulk.api.mailtrap.io"
1718
SANDBOX_HOST = "sandbox.api.mailtrap.io"
19+
TEMPLATES_HOST = "mailtrap.io"
1820

1921
def __init__(
2022
self,
2123
token: str,
2224
api_host: Optional[str] = None,
2325
api_port: int = DEFAULT_PORT,
26+
app_host: Optional[str] = None,
2427
bulk: bool = False,
2528
sandbox: bool = False,
2629
inbox_id: Optional[str] = None,
2730
) -> None:
2831
self.token = token
2932
self.api_host = api_host
3033
self.api_port = api_port
34+
self.app_host = app_host
3135
self.bulk = bulk
3236
self.sandbox = sandbox
3337
self.inbox_id = inbox_id
@@ -45,10 +49,60 @@ def send(self, mail: BaseMail) -> dict[str, Union[bool, list[str]]]:
4549

4650
self._handle_failed_response(response)
4751

52+
def email_templates(self, account_id: int) -> list[dict[str, Any]]:
53+
response = requests.get(self._templates_url(account_id), headers=self.headers)
54+
55+
if response.ok:
56+
data: list[dict[str, Any]] = response.json()
57+
return data
58+
59+
self._handle_failed_response(response)
60+
61+
def create_email_template(
62+
self, account_id: int, data: dict[str, Any]
63+
) -> dict[str, Any]:
64+
response = requests.post(
65+
self._templates_url(account_id), headers=self.headers, json=data
66+
)
67+
68+
if response.status_code == 201:
69+
return response.json()
70+
71+
self._handle_failed_response(response)
72+
73+
def update_email_template(
74+
self, account_id: int, template_id: int, data: dict[str, Any]
75+
) -> dict[str, Any]:
76+
response = requests.patch(
77+
self._templates_url(account_id, template_id),
78+
headers=self.headers,
79+
json=data,
80+
)
81+
82+
if response.ok:
83+
return response.json()
84+
85+
self._handle_failed_response(response)
86+
87+
def delete_email_template(self, account_id: int, template_id: int) -> None:
88+
response = requests.delete(
89+
self._templates_url(account_id, template_id), headers=self.headers
90+
)
91+
92+
if response.status_code == 204:
93+
return None
94+
95+
self._handle_failed_response(response)
96+
4897
@property
4998
def base_url(self) -> str:
5099
return f"https://{self._host.rstrip('/')}:{self.api_port}"
51100

101+
@property
102+
def app_base_url(self) -> str:
103+
host = self.app_host if self.app_host else self.TEMPLATES_HOST
104+
return f"https://{host.rstrip('/')}"
105+
52106
@property
53107
def api_send_url(self) -> str:
54108
url = f"{self.base_url}/api/send"
@@ -67,6 +121,12 @@ def headers(self) -> dict[str, str]:
67121
),
68122
}
69123

124+
def _templates_url(self, account_id: int, template_id: Optional[int] = None) -> str:
125+
url = f"{self.app_base_url}/api/accounts/{account_id}/email_templates"
126+
if template_id is not None:
127+
url = f"{url}/{template_id}"
128+
return url
129+
70130
@property
71131
def _host(self) -> str:
72132
if self.api_host:

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "mailtrap"
3-
version = "2.1.0"
3+
version = "2.2.0"
44
description = "Official mailtrap.io API client"
55
readme = "README.md"
66
license = {file = "LICENSE.txt"}

tests/unit/test_client.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,68 @@ def test_send_should_raise_api_error_for_500_status_code(
142142

143143
with pytest.raises(mt.APIError):
144144
client.send(mail)
145+
146+
TEMPLATES_URL = "https://mailtrap.io/api/accounts/1/email_templates"
147+
TEMPLATE_DETAIL_URL = "https://mailtrap.io/api/accounts/1/email_templates/5"
148+
149+
@responses.activate
150+
def test_email_templates_should_return_list(self) -> None:
151+
response_body = [{"id": 1}, {"id": 2}]
152+
responses.add(responses.GET, self.TEMPLATES_URL, json=response_body)
153+
154+
client = self.get_client()
155+
result = client.email_templates(1)
156+
157+
assert result == response_body
158+
assert len(responses.calls) == 1
159+
request = responses.calls[0].request # type: ignore
160+
assert request.headers.items() >= client.headers.items()
161+
162+
@responses.activate
163+
def test_create_email_template_should_return_created_template(self) -> None:
164+
request_body = {"name": "Template"}
165+
response_body = {"id": 5}
166+
responses.add(
167+
responses.POST,
168+
self.TEMPLATES_URL,
169+
json=response_body,
170+
status=201,
171+
)
172+
173+
client = self.get_client()
174+
result = client.create_email_template(1, request_body)
175+
176+
assert result == response_body
177+
request = responses.calls[0].request # type: ignore
178+
assert request.body == json.dumps(request_body).encode()
179+
180+
@responses.activate
181+
def test_update_email_template_should_return_updated_template(self) -> None:
182+
request_body = {"name": "Template"}
183+
response_body = {"id": 5, "name": "Template"}
184+
responses.add(
185+
responses.PATCH,
186+
self.TEMPLATE_DETAIL_URL,
187+
json=response_body,
188+
)
189+
190+
client = self.get_client()
191+
result = client.update_email_template(1, 5, request_body)
192+
193+
assert result == response_body
194+
request = responses.calls[0].request # type: ignore
195+
assert request.body == json.dumps(request_body).encode()
196+
197+
@responses.activate
198+
def test_delete_email_template_should_return_none(self) -> None:
199+
responses.add(
200+
responses.DELETE,
201+
self.TEMPLATE_DETAIL_URL,
202+
status=204,
203+
)
204+
205+
client = self.get_client()
206+
result = client.delete_email_template(1, 5)
207+
208+
assert result is None
209+
assert len(responses.calls) == 1

0 commit comments

Comments
 (0)