utobo-email 1.0.0__tar.gz

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.
@@ -0,0 +1,39 @@
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ *.local
14
+
15
+ # Editor directories and files
16
+ .vscode/*
17
+ !.vscode/extensions.json
18
+ .idea
19
+ .DS_Store
20
+ **/.DS_Store
21
+ *.suo
22
+ *.ntvs*
23
+ *.njsproj
24
+ *.sln
25
+ *.sw?
26
+
27
+ # Environment variables
28
+ .env
29
+ .env.local
30
+ .env.*.local
31
+
32
+ # Vite temp files
33
+ vite.config.ts.timestamp-*.mjs
34
+ dist/robots.txt
35
+ node_modules/
36
+ .gstack/
37
+
38
+ # Local Claude settings (do not commit)
39
+ .claude/settings.local.json
@@ -0,0 +1,68 @@
1
+ Metadata-Version: 2.4
2
+ Name: utobo-email
3
+ Version: 1.0.0
4
+ Summary: Official Python SDK for the utobo email Email Platform
5
+ Project-URL: Homepage, https://utobo.com/email
6
+ Project-URL: Documentation, https://utobo.com/email/docs
7
+ Project-URL: Repository, https://github.com/myutobo/utobo-email-python
8
+ Project-URL: Issues, https://github.com/myutobo/utobo-email-python/issues
9
+ Author-email: Utobo <hello@mail.utobo.com>
10
+ License-Expression: MIT
11
+ Keywords: api,email,sdk,transactional,utobo
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Communications :: Email
22
+ Requires-Python: >=3.8
23
+ Requires-Dist: httpx>=0.24.0
24
+ Description-Content-Type: text/markdown
25
+
26
+ # utobo-email
27
+
28
+ Official Python SDK for the [utobo email](https://utobo.com/email) email platform.
29
+
30
+ ## Install
31
+
32
+ ```bash
33
+ pip install utobo-email
34
+ ```
35
+
36
+ ## Quickstart
37
+
38
+ ```python
39
+ from utobo_email import UtoboEmail
40
+
41
+ client = UtoboEmail("av_live_...")
42
+
43
+ result = client.emails.send(
44
+ from_="Acme <noreply@acme.com>",
45
+ to="user@example.com",
46
+ subject="Welcome!",
47
+ html="<p>Welcome to Acme!</p>",
48
+ )
49
+ print(result["id"])
50
+ ```
51
+
52
+ ## Check delivery status
53
+
54
+ ```python
55
+ status = client.emails.get("3c9b0f4e-...")
56
+ print(status["status"]) # "sent" | "failed"
57
+ ```
58
+
59
+ ## Configuration
60
+
61
+ | Option | Env var | Default |
62
+ |--------|---------|---------|
63
+ | `api_key` | `UTOBO_EMAIL_API_KEY` | — (required) |
64
+ | `base_url` | `UTOBO_EMAIL_BASE_URL` | `https://api.email.utobo.com/v1` |
65
+
66
+ ## License
67
+
68
+ MIT
@@ -0,0 +1,43 @@
1
+ # utobo-email
2
+
3
+ Official Python SDK for the [utobo email](https://utobo.com/email) email platform.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install utobo-email
9
+ ```
10
+
11
+ ## Quickstart
12
+
13
+ ```python
14
+ from utobo_email import UtoboEmail
15
+
16
+ client = UtoboEmail("av_live_...")
17
+
18
+ result = client.emails.send(
19
+ from_="Acme <noreply@acme.com>",
20
+ to="user@example.com",
21
+ subject="Welcome!",
22
+ html="<p>Welcome to Acme!</p>",
23
+ )
24
+ print(result["id"])
25
+ ```
26
+
27
+ ## Check delivery status
28
+
29
+ ```python
30
+ status = client.emails.get("3c9b0f4e-...")
31
+ print(status["status"]) # "sent" | "failed"
32
+ ```
33
+
34
+ ## Configuration
35
+
36
+ | Option | Env var | Default |
37
+ |--------|---------|---------|
38
+ | `api_key` | `UTOBO_EMAIL_API_KEY` | — (required) |
39
+ | `base_url` | `UTOBO_EMAIL_BASE_URL` | `https://api.email.utobo.com/v1` |
40
+
41
+ ## License
42
+
43
+ MIT
@@ -0,0 +1,32 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "utobo-email"
7
+ version = "1.0.0"
8
+ description = "Official Python SDK for the utobo email Email Platform"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.8"
12
+ authors = [{ name = "Utobo", email = "hello@mail.utobo.com" }]
13
+ keywords = ["utobo", "email", "api", "sdk", "transactional"]
14
+ classifiers = [
15
+ "Development Status :: 5 - Production/Stable",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.8",
20
+ "Programming Language :: Python :: 3.9",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Topic :: Communications :: Email",
25
+ ]
26
+ dependencies = ["httpx>=0.24.0"]
27
+
28
+ [project.urls]
29
+ Homepage = "https://utobo.com/email"
30
+ Documentation = "https://utobo.com/email/docs"
31
+ Repository = "https://github.com/myutobo/utobo-email-python"
32
+ Issues = "https://github.com/myutobo/utobo-email-python/issues"
@@ -0,0 +1,5 @@
1
+ from .client import UtoboEmail
2
+ from .exceptions import UtoboEmailError, ApiError, NetworkError
3
+
4
+ __all__ = ["UtoboEmail", "UtoboEmailError", "ApiError", "NetworkError"]
5
+ __version__ = "1.0.0"
@@ -0,0 +1,106 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from typing import Any, Dict, Optional
5
+
6
+ import httpx
7
+
8
+ from .emails import Emails
9
+ from .exceptions import ApiError, NetworkError
10
+
11
+ DEFAULT_BASE_URL = "https://api.email.utobo.com/v1"
12
+ SDK_VERSION = "1.0.0"
13
+
14
+
15
+ class UtoboEmail:
16
+ """utobo email Python SDK client.
17
+
18
+ Usage:
19
+ from utobo_email import UtoboEmail
20
+
21
+ client = UtoboEmail("av_live_...")
22
+ result = client.emails.send(
23
+ from_="Acme <noreply@acme.com>",
24
+ to="user@example.com",
25
+ subject="Welcome!",
26
+ html="<p>Welcome to Acme!</p>",
27
+ )
28
+ print(result["id"])
29
+ """
30
+
31
+ def __init__(
32
+ self,
33
+ api_key: Optional[str] = None,
34
+ *,
35
+ base_url: Optional[str] = None,
36
+ ):
37
+ resolved_key = api_key or os.environ.get("UTOBO_EMAIL_API_KEY") or os.environ.get("UTOBO_MAIL_API_KEY")
38
+ if not resolved_key:
39
+ raise ValueError(
40
+ "Missing API key. "
41
+ 'Pass it to the constructor `UtoboEmail("av_live_...")` '
42
+ "or set the UTOBO_EMAIL_API_KEY environment variable."
43
+ )
44
+
45
+ self._api_key = resolved_key
46
+ self._base_url = (
47
+ base_url
48
+ or os.environ.get("UTOBO_EMAIL_BASE_URL")
49
+ or os.environ.get("UTOBO_MAIL_BASE_URL")
50
+ or DEFAULT_BASE_URL
51
+ )
52
+ self._http = httpx.Client(
53
+ base_url=self._base_url,
54
+ headers={
55
+ "Authorization": f"Bearer {self._api_key}",
56
+ "Content-Type": "application/json",
57
+ "User-Agent": f"utobo-email-python/{SDK_VERSION}",
58
+ },
59
+ timeout=30.0,
60
+ )
61
+
62
+ self.emails = Emails(self)
63
+
64
+ def _post(self, path: str, body: Dict[str, Any]) -> Dict[str, Any]:
65
+ try:
66
+ resp = self._http.post(path, json=body)
67
+ except httpx.HTTPError as e:
68
+ raise NetworkError(str(e)) from e
69
+
70
+ if not resp.is_success:
71
+ self._raise_api_error(resp)
72
+ return resp.json()
73
+
74
+ def _get(self, path: str) -> Dict[str, Any]:
75
+ try:
76
+ resp = self._http.get(path)
77
+ except httpx.HTTPError as e:
78
+ raise NetworkError(str(e)) from e
79
+
80
+ if not resp.is_success:
81
+ self._raise_api_error(resp)
82
+ return resp.json()
83
+
84
+ def _raise_api_error(self, resp: httpx.Response) -> None:
85
+ try:
86
+ data = resp.json()
87
+ raise ApiError(
88
+ status_code=resp.status_code,
89
+ name=data.get("name", "api_error"),
90
+ message=data.get("message", resp.reason_phrase or "Request failed"),
91
+ )
92
+ except (ValueError, KeyError):
93
+ raise ApiError(
94
+ status_code=resp.status_code,
95
+ name="api_error",
96
+ message=resp.reason_phrase or "Request failed",
97
+ )
98
+
99
+ def close(self) -> None:
100
+ self._http.close()
101
+
102
+ def __enter__(self) -> "UtoboEmail":
103
+ return self
104
+
105
+ def __exit__(self, *_: Any) -> None:
106
+ self.close()
@@ -0,0 +1,61 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Dict, List, Optional, Union
4
+
5
+ from .exceptions import ApiError, NetworkError
6
+
7
+ if False: # TYPE_CHECKING
8
+ from .client import UtoboEmail
9
+
10
+
11
+ class Emails:
12
+ def __init__(self, client: "UtoboEmail"):
13
+ self._client = client
14
+
15
+ def send(
16
+ self,
17
+ *,
18
+ from_: str,
19
+ to: Union[str, List[str]],
20
+ subject: str,
21
+ html: Optional[str] = None,
22
+ text: Optional[str] = None,
23
+ reply_to: Optional[Union[str, List[str]]] = None,
24
+ cc: Optional[Union[str, List[str]]] = None,
25
+ bcc: Optional[Union[str, List[str]]] = None,
26
+ attachments: Optional[List[Dict[str, Any]]] = None,
27
+ headers: Optional[Dict[str, str]] = None,
28
+ ) -> Dict[str, Any]:
29
+ """Send a transactional email.
30
+
31
+ Returns dict with 'id' and 'message' on success.
32
+ Raises ApiError or NetworkError on failure.
33
+
34
+ Each attachment dict requires 'filename' and 'content' (base64 string),
35
+ and an optional 'contentType'.
36
+ """
37
+ body: Dict[str, Any] = {
38
+ "from": from_,
39
+ "to": [to] if isinstance(to, str) else to,
40
+ "subject": subject,
41
+ }
42
+ if html is not None:
43
+ body["html"] = html
44
+ if text is not None:
45
+ body["text"] = text
46
+ if reply_to is not None:
47
+ body["reply_to"] = [reply_to] if isinstance(reply_to, str) else reply_to
48
+ if cc is not None:
49
+ body["cc"] = [cc] if isinstance(cc, str) else cc
50
+ if bcc is not None:
51
+ body["bcc"] = [bcc] if isinstance(bcc, str) else bcc
52
+ if attachments is not None:
53
+ body["attachments"] = attachments
54
+ if headers is not None:
55
+ body["headers"] = headers
56
+
57
+ return self._client._post("/send-email", body)
58
+
59
+ def get(self, email_id: str) -> Dict[str, Any]:
60
+ """Retrieve the status of a previously sent email."""
61
+ return self._client._get(f"/send-email/{email_id}")
@@ -0,0 +1,16 @@
1
+ class UtoboEmailError(Exception):
2
+ pass
3
+
4
+
5
+ class ApiError(UtoboEmailError):
6
+ def __init__(self, status_code: int, name: str, message: str):
7
+ self.status_code = status_code
8
+ self.name = name
9
+ self.message = message
10
+ super().__init__(f"{name} ({status_code}): {message}")
11
+
12
+
13
+ class NetworkError(UtoboEmailError):
14
+ def __init__(self, message: str = "Unable to reach the utobo email API"):
15
+ self.message = message
16
+ super().__init__(message)