emailalias 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
emailalias/__init__.py ADDED
@@ -0,0 +1,11 @@
1
+ from .client import Client
2
+ from .errors import EmailAliasError, AuthenticationError, RateLimitError, NotFoundError
3
+
4
+ __all__ = [
5
+ "Client",
6
+ "EmailAliasError",
7
+ "AuthenticationError",
8
+ "RateLimitError",
9
+ "NotFoundError",
10
+ ]
11
+ __version__ = "1.0.0"
emailalias/client.py ADDED
@@ -0,0 +1,176 @@
1
+ from typing import Any, Dict, List, Optional
2
+
3
+ import requests
4
+
5
+ from .errors import (
6
+ AuthenticationError,
7
+ EmailAliasError,
8
+ NotFoundError,
9
+ RateLimitError,
10
+ )
11
+
12
+ DEFAULT_BASE_URL = "https://emailalias.io"
13
+
14
+
15
+ class Client:
16
+ """Synchronous client for the EmailAlias REST API.
17
+
18
+ >>> from emailalias import Client
19
+ >>> client = Client(api_key="ea_live_xxx")
20
+ >>> client.list_aliases()
21
+ """
22
+
23
+ def __init__(
24
+ self,
25
+ api_key: str,
26
+ base_url: str = DEFAULT_BASE_URL,
27
+ timeout: float = 30.0,
28
+ session: Optional[requests.Session] = None,
29
+ ):
30
+ if not api_key:
31
+ raise ValueError("api_key is required")
32
+ self.api_key = api_key
33
+ self.base_url = base_url.rstrip("/")
34
+ self.timeout = timeout
35
+ self._session = session or requests.Session()
36
+
37
+ # ── Low-level transport ──────────────────────────────────────────────
38
+ def _request(self, method: str, path: str, json: Any = None) -> Any:
39
+ url = f"{self.base_url}{path}"
40
+ res = self._session.request(
41
+ method,
42
+ url,
43
+ json=json,
44
+ headers={
45
+ "Authorization": f"Bearer {self.api_key}",
46
+ "Accept": "application/json",
47
+ },
48
+ timeout=self.timeout,
49
+ )
50
+
51
+ if res.status_code == 204:
52
+ return None
53
+
54
+ try:
55
+ body = res.json()
56
+ except ValueError:
57
+ body = {"detail": res.text}
58
+
59
+ if not res.ok:
60
+ detail = body.get("detail") if isinstance(body, dict) else None
61
+ message = detail if isinstance(detail, str) else str(body)
62
+ cls = {
63
+ 401: AuthenticationError,
64
+ 404: NotFoundError,
65
+ 429: RateLimitError,
66
+ }.get(res.status_code, EmailAliasError)
67
+ raise cls(message, status=res.status_code)
68
+
69
+ return body
70
+
71
+ # ── Aliases ───────────────────────────────────────────────────────────
72
+ def list_aliases(self) -> List[Dict[str, Any]]:
73
+ return self._request("GET", "/api/aliases")
74
+
75
+ def create_alias(
76
+ self,
77
+ alias_type: str = "random",
78
+ label: Optional[str] = None,
79
+ domain: Optional[str] = None,
80
+ destination_email: Optional[str] = None,
81
+ custom_code: Optional[str] = None,
82
+ tag: Optional[str] = None,
83
+ ) -> Dict[str, Any]:
84
+ body: Dict[str, Any] = {"alias_type": alias_type}
85
+ if label is not None:
86
+ body["label"] = label
87
+ if domain is not None:
88
+ body["domain"] = domain
89
+ if destination_email is not None:
90
+ body["destination_email"] = destination_email
91
+ if custom_code is not None:
92
+ body["custom_code"] = custom_code
93
+ if tag is not None:
94
+ body["tag"] = tag
95
+ return self._request("POST", "/api/aliases", json=body)
96
+
97
+ def update_alias(
98
+ self,
99
+ alias_id: str,
100
+ *,
101
+ active: Optional[bool] = None,
102
+ label: Optional[str] = None,
103
+ ) -> Dict[str, Any]:
104
+ body: Dict[str, Any] = {}
105
+ if active is not None:
106
+ body["active"] = active
107
+ if label is not None:
108
+ body["label"] = label
109
+ return self._request("PATCH", f"/api/aliases/{alias_id}", json=body)
110
+
111
+ def delete_alias(self, alias_id: str) -> None:
112
+ self._request("DELETE", f"/api/aliases/{alias_id}")
113
+
114
+ def list_available_domains(self) -> List[Dict[str, Any]]:
115
+ return self._request("GET", "/api/aliases/domains")
116
+
117
+ # ── Destinations ──────────────────────────────────────────────────────
118
+ def list_destinations(self) -> List[Dict[str, Any]]:
119
+ return self._request("GET", "/api/destinations")
120
+
121
+ def add_destination(self, email: str) -> Dict[str, Any]:
122
+ return self._request("POST", "/api/destinations", json={"email": email})
123
+
124
+ def resend_destination_verification(self, destination_id: str) -> Dict[str, Any]:
125
+ return self._request("POST", f"/api/destinations/{destination_id}/resend")
126
+
127
+ def delete_destination(self, destination_id: str) -> None:
128
+ self._request("DELETE", f"/api/destinations/{destination_id}")
129
+
130
+ # ── Send email ────────────────────────────────────────────────────────
131
+ def send_email(
132
+ self,
133
+ alias_id: str,
134
+ to_email: str,
135
+ subject: str,
136
+ body: str,
137
+ html_body: Optional[str] = None,
138
+ ) -> Dict[str, Any]:
139
+ payload: Dict[str, Any] = {
140
+ "alias_id": alias_id,
141
+ "to_email": to_email,
142
+ "subject": subject,
143
+ "body": body,
144
+ }
145
+ if html_body is not None:
146
+ payload["html_body"] = html_body
147
+ return self._request("POST", "/api/send-email", json=payload)
148
+
149
+ # ── Custom Domains ────────────────────────────────────────────────────
150
+ def list_domains(self) -> List[Dict[str, Any]]:
151
+ return self._request("GET", "/api/domains")
152
+
153
+ def add_domain(self, domain_name: str) -> Dict[str, Any]:
154
+ return self._request("POST", "/api/domains", json={"domain_name": domain_name})
155
+
156
+ def verify_domain(self, domain_id: str) -> Dict[str, Any]:
157
+ return self._request("POST", f"/api/domains/{domain_id}/verify")
158
+
159
+ def delete_domain(self, domain_id: str) -> None:
160
+ self._request("DELETE", f"/api/domains/{domain_id}")
161
+
162
+ # ── Analytics ─────────────────────────────────────────────────────────
163
+ def get_dashboard_stats(self) -> Dict[str, Any]:
164
+ return self._request("GET", "/api/analytics/dashboard")
165
+
166
+ def list_logs(self, page: int = 1, per_page: int = 25) -> Dict[str, Any]:
167
+ return self._request(
168
+ "GET", f"/api/analytics/logs?page={page}&per_page={per_page}"
169
+ )
170
+
171
+ def list_exposure_events(
172
+ self, page: int = 1, per_page: int = 25
173
+ ) -> Dict[str, Any]:
174
+ return self._request(
175
+ "GET", f"/api/analytics/exposure?page={page}&per_page={per_page}"
176
+ )
emailalias/errors.py ADDED
@@ -0,0 +1,19 @@
1
+ class EmailAliasError(Exception):
2
+ """Base exception. `status` is the HTTP code, `message` the server detail."""
3
+
4
+ def __init__(self, message: str, status: int = 0):
5
+ super().__init__(message)
6
+ self.status = status
7
+ self.message = message
8
+
9
+
10
+ class AuthenticationError(EmailAliasError):
11
+ """401 — invalid API key, or the key's owner is no longer Premium."""
12
+
13
+
14
+ class NotFoundError(EmailAliasError):
15
+ """404."""
16
+
17
+
18
+ class RateLimitError(EmailAliasError):
19
+ """429. Honour the `X-RateLimit-Reset` header before retrying."""
@@ -0,0 +1,145 @@
1
+ Metadata-Version: 2.4
2
+ Name: emailalias
3
+ Version: 1.0.0
4
+ Summary: Official Python client for the EmailAlias API.
5
+ Author: EmailAlias
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 EmailAlias.io
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://emailalias.io
29
+ Project-URL: Documentation, https://emailalias.io/documentation
30
+ Project-URL: Repository, https://github.com/emailalias/emailalias-python
31
+ Classifier: Programming Language :: Python :: 3
32
+ Classifier: License :: OSI Approved :: MIT License
33
+ Classifier: Operating System :: OS Independent
34
+ Requires-Python: >=3.8
35
+ Description-Content-Type: text/markdown
36
+ License-File: LICENSE
37
+ Requires-Dist: requests>=2.28
38
+ Dynamic: license-file
39
+
40
+ # emailalias-python
41
+
42
+ Official Python client for the [EmailAlias.io](https://emailalias.io) REST API.
43
+
44
+ API access is a **Premium** feature. Generate a key from **Settings → API Keys** in the web dashboard.
45
+
46
+ ## Install
47
+
48
+ ```bash
49
+ pip install emailalias
50
+ ```
51
+
52
+ Or from source:
53
+
54
+ ```bash
55
+ pip install git+https://github.com/emailalias/emailalias-python.git
56
+ ```
57
+
58
+ ## Quick start
59
+
60
+ ```python
61
+ from emailalias import Client
62
+
63
+ client = Client(api_key="ea_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
64
+
65
+ # Create an alias
66
+ alias = client.create_alias(alias_type="random", label="Shopping")
67
+ print(alias["alias_email"]) # e.g. "x7k9m@email91.com"
68
+
69
+ # List aliases
70
+ for a in client.list_aliases():
71
+ print(a["alias_email"], "→", a["destination_email"])
72
+
73
+ # Forward to a verified additional destination
74
+ alias = client.create_alias(
75
+ alias_type="custom",
76
+ custom_code="work-signup",
77
+ label="Work",
78
+ destination_email="work@mycompany.com", # must be verified on your account first
79
+ )
80
+
81
+ # Send email from an alias
82
+ client.send_email(
83
+ alias_id=alias["id"],
84
+ to_email="recipient@example.com",
85
+ subject="Hello",
86
+ body="Sent from my alias.",
87
+ )
88
+
89
+ # Disable an alias
90
+ client.update_alias(alias_id=alias["id"], active=False)
91
+ ```
92
+
93
+ ## Error handling
94
+
95
+ ```python
96
+ from emailalias import Client, AuthenticationError, RateLimitError
97
+
98
+ client = Client(api_key="ea_live_xxx")
99
+ try:
100
+ client.list_aliases()
101
+ except AuthenticationError:
102
+ # Invalid key, or account is no longer Premium
103
+ ...
104
+ except RateLimitError:
105
+ # Respect X-RateLimit-Reset and retry
106
+ ...
107
+ ```
108
+
109
+ ## Configuration
110
+
111
+ ```python
112
+ client = Client(
113
+ api_key="ea_live_xxx",
114
+ base_url="https://emailalias.io", # override for staging/self-host
115
+ timeout=30.0,
116
+ )
117
+ ```
118
+
119
+ ## Available methods
120
+
121
+ | Method | Endpoint |
122
+ |---|---|
123
+ | `list_aliases()` | `GET /api/aliases` |
124
+ | `create_alias(...)` | `POST /api/aliases` |
125
+ | `update_alias(id, active=, label=)` | `PATCH /api/aliases/{id}` |
126
+ | `delete_alias(id)` | `DELETE /api/aliases/{id}` |
127
+ | `list_available_domains()` | `GET /api/aliases/domains` |
128
+ | `list_destinations()` | `GET /api/destinations` |
129
+ | `add_destination(email)` | `POST /api/destinations` |
130
+ | `resend_destination_verification(id)` | `POST /api/destinations/{id}/resend` |
131
+ | `delete_destination(id)` | `DELETE /api/destinations/{id}` |
132
+ | `send_email(alias_id, to_email, subject, body, html_body=)` | `POST /api/send-email` |
133
+ | `list_domains()` | `GET /api/domains` |
134
+ | `add_domain(name)` | `POST /api/domains` |
135
+ | `verify_domain(id)` | `POST /api/domains/{id}/verify` |
136
+ | `delete_domain(id)` | `DELETE /api/domains/{id}` |
137
+ | `get_dashboard_stats()` | `GET /api/analytics/dashboard` |
138
+ | `list_logs(page=, per_page=)` | `GET /api/analytics/logs` |
139
+ | `list_exposure_events(page=, per_page=)` | `GET /api/analytics/exposure` |
140
+
141
+ Full API reference: <https://emailalias.io/documentation>
142
+
143
+ ## License
144
+
145
+ MIT
@@ -0,0 +1,8 @@
1
+ emailalias/__init__.py,sha256=Xjie9tvVf7k8oFcdSrvOhCv9a7dvMW9iLxSs2jf8_2o,259
2
+ emailalias/client.py,sha256=pfn3l28XOOydDjvSzk2twBfecfozvjn-vuFuyHR-7a8,6629
3
+ emailalias/errors.py,sha256=1l7Degm5nfgDkoTtRGuEHVz7yoVAItkERWguK6YZn1Y,554
4
+ emailalias-1.0.0.dist-info/licenses/LICENSE,sha256=VmZ1ukK2Gq9jDEVa893cqcDR69UK9KQECY0WGNUHyTo,1070
5
+ emailalias-1.0.0.dist-info/METADATA,sha256=IJndlEF1BTP4lxd6e_JcGI5ce3tRXdrXFEcDpFfRn5E,4696
6
+ emailalias-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
7
+ emailalias-1.0.0.dist-info/top_level.txt,sha256=ux14GRquM3pbi4hc6Sf8NOCmksJEBgP28OOUwbaQktc,11
8
+ emailalias-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 EmailAlias.io
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ emailalias