From a9bfcc4f122a2c477353d234497d57898acab904 Mon Sep 17 00:00:00 2001 From: Alex Shchyhol Date: Tue, 8 Sep 2026 11:01:02 +0200 Subject: [PATCH 1/5] add sub account deletion to SubAccountsApi --- mailtrap/api/resources/sub_accounts.py | 25 +++++++- .../api/organizations/test_sub_accounts.py | 57 +++++++++++++++++++ 2 files changed, 80 insertions(+), 2 deletions(-) diff --git a/mailtrap/api/resources/sub_accounts.py b/mailtrap/api/resources/sub_accounts.py index 2de47f9..cbe7a15 100644 --- a/mailtrap/api/resources/sub_accounts.py +++ b/mailtrap/api/resources/sub_accounts.py @@ -1,4 +1,7 @@ +from typing import Optional + from mailtrap.http import HttpClient +from mailtrap.models.common import DeletedObject from mailtrap.models.organizations import CreateSubAccountParams from mailtrap.models.organizations import SubAccount @@ -27,5 +30,23 @@ def create(self, sub_account_params: CreateSubAccountParams) -> SubAccount: ) return SubAccount(**response) - def _api_path(self) -> str: - return f"/api/organizations/{self._organization_id}/sub_accounts" + def delete(self, sub_account_id: int) -> DeletedObject: + """ + Delete a sub account of the organization. Requires sub account + management permissions for this organization. + + The sub account and all of its data are removed permanently and cannot + be restored. Deleting the organization's last sub account also deletes + the organization. A repeated call for the same sub account returns + a 404 error. + + Rate limit: 10 requests per minute per organization. + """ + self._client.delete(self._api_path(sub_account_id)) + return DeletedObject(id=sub_account_id) + + def _api_path(self, sub_account_id: Optional[int] = None) -> str: + path = f"/api/organizations/{self._organization_id}/sub_accounts" + if sub_account_id is not None: + return f"{path}/{sub_account_id}" + return path diff --git a/tests/unit/api/organizations/test_sub_accounts.py b/tests/unit/api/organizations/test_sub_accounts.py index bf90926..b617b8e 100644 --- a/tests/unit/api/organizations/test_sub_accounts.py +++ b/tests/unit/api/organizations/test_sub_accounts.py @@ -7,6 +7,7 @@ from mailtrap.config import GENERAL_HOST from mailtrap.exceptions import APIError from mailtrap.http import HttpClient +from mailtrap.models.common import DeletedObject from mailtrap.models.organizations import CreateSubAccountParams from mailtrap.models.organizations import SubAccount from tests import conftest @@ -152,3 +153,59 @@ def test_create_should_return_sub_account_and_wrap_body_under_account_key( responses.calls[0].request.body == b'{"account": {"name": "New Team Account"}}' ) + + @pytest.mark.parametrize( + "status_code,response_json,expected_error_message", + [ + ( + conftest.UNAUTHORIZED_STATUS_CODE, + conftest.UNAUTHORIZED_RESPONSE, + conftest.UNAUTHORIZED_ERROR_MESSAGE, + ), + ( + conftest.FORBIDDEN_STATUS_CODE, + conftest.FORBIDDEN_RESPONSE, + conftest.FORBIDDEN_ERROR_MESSAGE, + ), + ( + conftest.NOT_FOUND_STATUS_CODE, + conftest.NOT_FOUND_RESPONSE, + conftest.NOT_FOUND_ERROR_MESSAGE, + ), + ( + conftest.RATE_LIMIT_ERROR_STATUS_CODE, + conftest.RATE_LIMIT_ERROR_RESPONSE, + conftest.RATE_LIMIT_ERROR_MESSAGE, + ), + ], + ) + @responses.activate + def test_delete_should_raise_api_errors( + self, + client: SubAccountsApi, + status_code: int, + response_json: dict, + expected_error_message: str, + ) -> None: + responses.delete( + f"{BASE_SUB_ACCOUNTS_URL}/{SUB_ACCOUNT_ID}", + status=status_code, + json=response_json, + ) + + with pytest.raises(APIError) as exc_info: + client.delete(SUB_ACCOUNT_ID) + + assert expected_error_message in str(exc_info.value) + + @responses.activate + def test_delete_should_return_deleted_object(self, client: SubAccountsApi) -> None: + responses.delete( + f"{BASE_SUB_ACCOUNTS_URL}/{SUB_ACCOUNT_ID}", + status=204, + ) + + result = client.delete(SUB_ACCOUNT_ID) + + assert isinstance(result, DeletedObject) + assert result.id == SUB_ACCOUNT_ID From 91af1608de3bef12d33eeb15936c3a2a0ed59979 Mon Sep 17 00:00:00 2001 From: Alex Shchyhol Date: Tue, 8 Sep 2026 11:01:59 +0200 Subject: [PATCH 2/5] add sub account deletion to the example and README --- README.md | 2 +- examples/organizations/sub_accounts.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7621316..7383d21 100644 --- a/README.md +++ b/README.md @@ -279,7 +279,7 @@ The same situation applies to both `client.batch_send()` and `client.sending_api - Permissions listing – [`general/permissions.py`](examples/general/permissions.py) ### Organizations API: -- Sub-Accounts management – [`organizations/sub_accounts.py`](examples/organizations/sub_accounts.py) +- Sub-Accounts management (list, create, delete) – [`organizations/sub_accounts.py`](examples/organizations/sub_accounts.py) ## Contributing diff --git a/examples/organizations/sub_accounts.py b/examples/organizations/sub_accounts.py index 2379b3c..238a247 100644 --- a/examples/organizations/sub_accounts.py +++ b/examples/organizations/sub_accounts.py @@ -1,6 +1,7 @@ import os import mailtrap as mt +from mailtrap.models.common import DeletedObject from mailtrap.models.organizations import SubAccount API_KEY = os.environ["MAILTRAP_API_KEY"] @@ -18,9 +19,16 @@ def create_sub_account(name: str) -> SubAccount: return sub_accounts_api.create(mt.CreateSubAccountParams(name=name)) +def delete_sub_account(sub_account_id: int) -> DeletedObject: + return sub_accounts_api.delete(sub_account_id=sub_account_id) + + if __name__ == "__main__": sub_accounts = list_sub_accounts() print(sub_accounts) created = create_sub_account("New Team Account") print(created) + + deleted = delete_sub_account(created.id) + print(deleted) From 8e4e0c015516ab53a92325c3880bffea8b7e4c30 Mon Sep 17 00:00:00 2001 From: Alex Shchyhol Date: Wed, 4 Nov 2026 11:46:16 +0100 Subject: [PATCH 3/5] update the permission note for deleting a sub-account --- mailtrap/api/resources/sub_accounts.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mailtrap/api/resources/sub_accounts.py b/mailtrap/api/resources/sub_accounts.py index cbe7a15..8a033ec 100644 --- a/mailtrap/api/resources/sub_accounts.py +++ b/mailtrap/api/resources/sub_accounts.py @@ -32,8 +32,8 @@ def create(self, sub_account_params: CreateSubAccountParams) -> SubAccount: def delete(self, sub_account_id: int) -> DeletedObject: """ - Delete a sub account of the organization. Requires sub account - management permissions for this organization. + Delete a sub account of the organization. Requires organization + admin permissions. The sub account and all of its data are removed permanently and cannot be restored. Deleting the organization's last sub account also deletes From 5567c7fde41a62c74663a4d213b907ba8fda6f86 Mon Sep 17 00:00:00 2001 From: Alex Shchyhol Date: Thu, 5 Nov 2026 20:45:55 +0100 Subject: [PATCH 4/5] mention the Organization & Sub-accounts feature requirement for deleting a sub-account --- mailtrap/api/resources/sub_accounts.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mailtrap/api/resources/sub_accounts.py b/mailtrap/api/resources/sub_accounts.py index 8a033ec..2264385 100644 --- a/mailtrap/api/resources/sub_accounts.py +++ b/mailtrap/api/resources/sub_accounts.py @@ -33,7 +33,8 @@ def create(self, sub_account_params: CreateSubAccountParams) -> SubAccount: def delete(self, sub_account_id: int) -> DeletedObject: """ Delete a sub account of the organization. Requires organization - admin permissions. + admin permissions. The organization must also have the Organization + & Sub-accounts feature enabled. The sub account and all of its data are removed permanently and cannot be restored. Deleting the organization's last sub account also deletes From 2f58d9957e9f93bc5574afc97dbbff9e7f2340db Mon Sep 17 00:00:00 2001 From: Alex Shchyhol Date: Thu, 5 Nov 2026 21:22:17 +0100 Subject: [PATCH 5/5] document the conflict refusal for sub account deletion --- mailtrap/api/resources/sub_accounts.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mailtrap/api/resources/sub_accounts.py b/mailtrap/api/resources/sub_accounts.py index 2264385..63099bd 100644 --- a/mailtrap/api/resources/sub_accounts.py +++ b/mailtrap/api/resources/sub_accounts.py @@ -40,6 +40,7 @@ def delete(self, sub_account_id: int) -> DeletedObject: be restored. Deleting the organization's last sub account also deletes the organization. A repeated call for the same sub account returns a 404 error. + A concurrent deletion of the same sub account fails with 409. Rate limit: 10 requests per minute per organization. """