mailflat 0.4.0__tar.gz → 0.4.1__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.
- {mailflat-0.4.0 → mailflat-0.4.1}/PKG-INFO +15 -4
- {mailflat-0.4.0 → mailflat-0.4.1}/README.md +7 -2
- {mailflat-0.4.0 → mailflat-0.4.1}/pyproject.toml +11 -1
- {mailflat-0.4.0 → mailflat-0.4.1}/src/mailflat/__init__.py +7 -3
- {mailflat-0.4.0 → mailflat-0.4.1}/src/mailflat/client.py +43 -18
- {mailflat-0.4.0 → mailflat-0.4.1}/src/mailflat/errors.py +3 -3
- {mailflat-0.4.0 → mailflat-0.4.1}/src/mailflat/inbox.py +34 -9
- {mailflat-0.4.0 → mailflat-0.4.1}/src/mailflat/langchain.py +6 -5
- {mailflat-0.4.0 → mailflat-0.4.1}/src/mailflat/redact.py +17 -16
- {mailflat-0.4.0 → mailflat-0.4.1}/.gitignore +0 -0
- {mailflat-0.4.0 → mailflat-0.4.1}/LICENSE +0 -0
- {mailflat-0.4.0 → mailflat-0.4.1}/src/mailflat/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mailflat
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: Official Python SDK for MailFlat — disposable, automation-friendly email inboxes with one-line OTP retrieval.
|
|
5
5
|
Project-URL: Homepage, https://mailflat.net
|
|
6
6
|
Project-URL: Documentation, https://mailflat.net
|
|
@@ -14,10 +14,16 @@ Classifier: Intended Audience :: Developers
|
|
|
14
14
|
Classifier: License :: OSI Approved :: MIT License
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
16
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
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: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
23
|
Classifier: Topic :: Communications :: Email
|
|
18
24
|
Classifier: Topic :: Software Development :: Testing
|
|
19
25
|
Classifier: Typing :: Typed
|
|
20
|
-
Requires-Python: >=3.
|
|
26
|
+
Requires-Python: >=3.9
|
|
21
27
|
Requires-Dist: httpx<1,>=0.24
|
|
22
28
|
Provides-Extra: dev
|
|
23
29
|
Requires-Dist: langchain-core>=0.2; extra == 'dev'
|
|
@@ -105,9 +111,14 @@ for self-hosted / BYOD deployments. Supports use as a context manager (`with Mai
|
|
|
105
111
|
|
|
106
112
|
- `.links` — URLs found in the body (HTML hrefs first). For "click the verification link" flows.
|
|
107
113
|
- `.attachments` — metadata; `msg.attachments[0].download()` fetches the bytes.
|
|
114
|
+
⚠️ `send()` cannot attach files yet, so you cannot produce an attachment from the SDK
|
|
115
|
+
alone — send one from a normal mail client to test this path.
|
|
108
116
|
- `.spam` — `{score, required, is_spam, rules, scanner}`, or `None` when never scanned
|
|
109
|
-
(which is not the same as a score of 0).
|
|
110
|
-
|
|
117
|
+
(which is not the same as a score of 0). ⚠️ Spam scanning is currently **disabled** on
|
|
118
|
+
mailflat.net, so today this is `None` for every message.
|
|
119
|
+
- `.headers` — raw headers, exactly as they arrived. Names are case-insensitive per
|
|
120
|
+
RFC 5322 but this is a plain dict, so **do not index it**: the real key is `Message-ID`,
|
|
121
|
+
and `headers["Message-Id"]` raises `KeyError`. Use `.header("message-id")` or `.message_id`.
|
|
111
122
|
- `.mark_read()` / `.delete()` — act on this message directly.
|
|
112
123
|
|
|
113
124
|
## Errors
|
|
@@ -76,9 +76,14 @@ for self-hosted / BYOD deployments. Supports use as a context manager (`with Mai
|
|
|
76
76
|
|
|
77
77
|
- `.links` — URLs found in the body (HTML hrefs first). For "click the verification link" flows.
|
|
78
78
|
- `.attachments` — metadata; `msg.attachments[0].download()` fetches the bytes.
|
|
79
|
+
⚠️ `send()` cannot attach files yet, so you cannot produce an attachment from the SDK
|
|
80
|
+
alone — send one from a normal mail client to test this path.
|
|
79
81
|
- `.spam` — `{score, required, is_spam, rules, scanner}`, or `None` when never scanned
|
|
80
|
-
(which is not the same as a score of 0).
|
|
81
|
-
|
|
82
|
+
(which is not the same as a score of 0). ⚠️ Spam scanning is currently **disabled** on
|
|
83
|
+
mailflat.net, so today this is `None` for every message.
|
|
84
|
+
- `.headers` — raw headers, exactly as they arrived. Names are case-insensitive per
|
|
85
|
+
RFC 5322 but this is a plain dict, so **do not index it**: the real key is `Message-ID`,
|
|
86
|
+
and `headers["Message-Id"]` raises `KeyError`. Use `.header("message-id")` or `.message_id`.
|
|
82
87
|
- `.mark_read()` / `.delete()` — act on this message directly.
|
|
83
88
|
|
|
84
89
|
## Errors
|
|
@@ -10,7 +10,7 @@ name = "mailflat"
|
|
|
10
10
|
dynamic = ["version"]
|
|
11
11
|
description = "Official Python SDK for MailFlat — disposable, automation-friendly email inboxes with one-line OTP retrieval."
|
|
12
12
|
readme = "README.md"
|
|
13
|
-
requires-python = ">=3.
|
|
13
|
+
requires-python = ">=3.9"
|
|
14
14
|
license = { text = "MIT" }
|
|
15
15
|
authors = [{ name = "MailFlat", email = "support@mailflat.net" }]
|
|
16
16
|
keywords = ["email", "disposable-email", "otp", "automation", "testing", "ai-agents", "temporary-email"]
|
|
@@ -20,6 +20,16 @@ classifiers = [
|
|
|
20
20
|
"License :: OSI Approved :: MIT License",
|
|
21
21
|
"Programming Language :: Python :: 3",
|
|
22
22
|
"Programming Language :: Python :: 3 :: Only",
|
|
23
|
+
# Aralığın İKİ UCU gerçekten koşuldu (3.11 + 3.14, 53 test yeşil). Aradaki sürümler için
|
|
24
|
+
# ayrı bir iddia yok: sürüme özgü API kullanmıyoruz ve `from __future__ import annotations`
|
|
25
|
+
# her dosyada var. Alt sınır 3.8 → 3.9: 3.8 EOL ve HİÇ test edilmedi; test edilmemiş bir
|
|
26
|
+
# sürümü desteklediğini söylemek, PyPI'da kullanıcıyı yanlış filtrelemeye götürür.
|
|
27
|
+
"Programming Language :: Python :: 3.9",
|
|
28
|
+
"Programming Language :: Python :: 3.10",
|
|
29
|
+
"Programming Language :: Python :: 3.11",
|
|
30
|
+
"Programming Language :: Python :: 3.12",
|
|
31
|
+
"Programming Language :: Python :: 3.13",
|
|
32
|
+
"Programming Language :: Python :: 3.14",
|
|
23
33
|
"Topic :: Communications :: Email",
|
|
24
34
|
"Topic :: Software Development :: Testing",
|
|
25
35
|
"Typing :: Typed",
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
`pip install mailflat` → `from mailflat import MailFlat`.
|
|
4
4
|
|
|
5
|
-
NOT: Bu dosya YAYIN yüzeyi (`packages/`) → docstring'ler İngilizce (CLAUDE.md §3 istisnası).
|
|
6
|
-
|
|
7
5
|
Connected to:
|
|
8
6
|
- imports from: mailflat.client, mailflat.inbox, mailflat.errors
|
|
9
7
|
- imported by: user code, mailflat-mcp, mailflat.langchain
|
|
@@ -15,6 +13,12 @@ Key exports:
|
|
|
15
13
|
`NotFoundError`, `RateLimitError`, `APIError`, `OTPTimeoutError`, `EncryptedInboxError`
|
|
16
14
|
- `redact_secrets()` — strips secret fields from agent tool output (used by MCP/LangChain)
|
|
17
15
|
"""
|
|
16
|
+
# NOT: `packages/` YAYIN yüzeyi → docstring'ler İngilizce (CLAUDE.md §3).
|
|
17
|
+
# Bu satırın kendisi bilerek YORUM: aynı notu docstring'e koymak tam da kaçındığımız
|
|
18
|
+
# şeydi — `help()` docstring'i basar, yorumu basmaz.
|
|
19
|
+
# NOT: `packages/` YAYIN yüzeyi → docstring'ler İngilizce (CLAUDE.md §3).
|
|
20
|
+
# Bu satır bilerek YORUM: docstring'e Türkçe not koymak, tam da kaçındığımız şeydi —
|
|
21
|
+
# `help()` docstring'i basar, yorumu basmaz.
|
|
18
22
|
from __future__ import annotations
|
|
19
23
|
|
|
20
24
|
from .client import MailFlat
|
|
@@ -34,7 +38,7 @@ from .redact import redact_secrets
|
|
|
34
38
|
|
|
35
39
|
# Paketin TEK sürüm kaynağı — pyproject.toml bunu [tool.hatch.version] ile okur.
|
|
36
40
|
# Yayın = burayı yükselt, başka hiçbir yeri değil.
|
|
37
|
-
__version__ = "0.4.
|
|
41
|
+
__version__ = "0.4.1"
|
|
38
42
|
|
|
39
43
|
__all__ = [
|
|
40
44
|
"MailFlat",
|
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
One account API key (X-API-Key) creates inboxes, lists them, reads mail/OTP and sends mail.
|
|
4
4
|
Every higher layer (MCP, LangChain) calls this client, so behaviour lives in one place.
|
|
5
5
|
|
|
6
|
-
NOT: Bu dosya YAYIN yüzeyi (`packages/`) → docstring'ler İngilizce (CLAUDE.md §3 istisnası).
|
|
7
|
-
|
|
8
6
|
Connected to:
|
|
9
7
|
- imports from: mailflat.inbox, mailflat.errors (+ httpx)
|
|
10
8
|
- imported by: mailflat.__init__
|
|
@@ -15,6 +13,12 @@ Key exports:
|
|
|
15
13
|
- `.list()` — list of `Inbox`
|
|
16
14
|
- `.inbox(address)` — attach to an existing address
|
|
17
15
|
"""
|
|
16
|
+
# NOT: `packages/` YAYIN yüzeyi → docstring'ler İngilizce (CLAUDE.md §3).
|
|
17
|
+
# Bu satırın kendisi bilerek YORUM: aynı notu docstring'e koymak tam da kaçındığımız
|
|
18
|
+
# şeydi — `help()` docstring'i basar, yorumu basmaz.
|
|
19
|
+
# NOT: `packages/` YAYIN yüzeyi → docstring'ler İngilizce (CLAUDE.md §3).
|
|
20
|
+
# Bu satır bilerek YORUM: docstring'e Türkçe not koymak, tam da kaçındığımız şeydi —
|
|
21
|
+
# `help()` docstring'i basar, yorumu basmaz.
|
|
18
22
|
from __future__ import annotations
|
|
19
23
|
|
|
20
24
|
import logging
|
|
@@ -41,11 +45,9 @@ logger = logging.getLogger("mailflat")
|
|
|
41
45
|
|
|
42
46
|
|
|
43
47
|
def _retry_after_seconds(resp: httpx.Response) -> float | None:
|
|
44
|
-
"""Parse the `Retry-After` header (seconds form). Returns None when absent/unparsable.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
hem de rate limit penceresini uzatmak demek.
|
|
48
|
-
"""
|
|
48
|
+
"""Parse the `Retry-After` header (seconds form). Returns None when absent/unparsable."""
|
|
49
|
+
# Sunucu "12 saniye bekle" derken 1 saniyede dönmek hem 429'u tekrar yemek hem de
|
|
50
|
+
# rate limit penceresini uzatmak demekti.
|
|
49
51
|
raw = resp.headers.get("Retry-After")
|
|
50
52
|
if not raw:
|
|
51
53
|
return None
|
|
@@ -95,9 +97,9 @@ class MailFlat:
|
|
|
95
97
|
def user_agent() -> str:
|
|
96
98
|
"""Return the `User-Agent` this client sends, e.g. `mailflat-python/0.4.0`.
|
|
97
99
|
|
|
98
|
-
|
|
99
|
-
döngüsel import'a girmeden okunur.
|
|
100
|
+
Sending the version lets the server tell which SDK build is misbehaving.
|
|
100
101
|
"""
|
|
102
|
+
# Sürüm burada okunuyor ki modül seviyesinde döngüsel import oluşmasın.
|
|
101
103
|
from . import __version__
|
|
102
104
|
|
|
103
105
|
return f"mailflat-python/{__version__}"
|
|
@@ -113,7 +115,13 @@ class MailFlat:
|
|
|
113
115
|
self._http.close()
|
|
114
116
|
|
|
115
117
|
# --------------------------------------------------------------- HTTP iç
|
|
116
|
-
def _request(self, method: str, path: str, json: dict[str, Any] | None = None
|
|
118
|
+
def _request(self, method: str, path: str, json: dict[str, Any] | None = None,
|
|
119
|
+
*, idempotent: bool | None = None) -> dict[str, Any]:
|
|
120
|
+
"""`idempotent=True` → metodu ne olursa olsun retry edilebilir sayılır.
|
|
121
|
+
|
|
122
|
+
mark_read/burn POST ama tekrarı zararsız (aynı sonuç). send için ASLA verilmez:
|
|
123
|
+
tekrarlanan bir send aynı maili ikinci kez teslim eder (B-058).
|
|
124
|
+
"""
|
|
117
125
|
attempt = 0
|
|
118
126
|
while True:
|
|
119
127
|
try:
|
|
@@ -122,9 +130,10 @@ class MailFlat:
|
|
|
122
130
|
raise MailFlatError(f"Network error: {exc}") from exc
|
|
123
131
|
|
|
124
132
|
retry_after = _retry_after_seconds(resp)
|
|
133
|
+
can_repeat = method.upper() in _IDEMPOTENT_METHODS if idempotent is None else idempotent
|
|
125
134
|
retriable = (
|
|
126
135
|
resp.status_code in _RETRY_STATUSES
|
|
127
|
-
and
|
|
136
|
+
and can_repeat
|
|
128
137
|
and attempt < self.max_retries
|
|
129
138
|
)
|
|
130
139
|
if retriable:
|
|
@@ -155,18 +164,34 @@ class MailFlat:
|
|
|
155
164
|
def _get(self, path: str) -> dict[str, Any]:
|
|
156
165
|
return self._request("GET", path)
|
|
157
166
|
|
|
158
|
-
def _post(self, path: str, json: dict[str, Any] | None = None
|
|
159
|
-
|
|
167
|
+
def _post(self, path: str, json: dict[str, Any] | None = None,
|
|
168
|
+
*, idempotent: bool = False) -> dict[str, Any]:
|
|
169
|
+
return self._request("POST", path, json=json or {}, idempotent=idempotent)
|
|
160
170
|
|
|
161
171
|
def _delete(self, path: str) -> dict[str, Any]:
|
|
162
172
|
return self._request("DELETE", path)
|
|
163
173
|
|
|
164
174
|
def _get_bytes(self, path: str) -> httpx.Response:
|
|
165
|
-
"""Raw response for binary endpoints (attachment download).
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
175
|
+
"""Raw response for binary endpoints (attachment download).
|
|
176
|
+
|
|
177
|
+
Retries on the same statuses as any other GET: this path bypassed the retry loop
|
|
178
|
+
entirely until 0.4.1, so an attachment download gave up on the first 503 while
|
|
179
|
+
every other read retried.
|
|
180
|
+
"""
|
|
181
|
+
attempt = 0
|
|
182
|
+
while True:
|
|
183
|
+
try:
|
|
184
|
+
resp = self._http.request("GET", path)
|
|
185
|
+
except httpx.HTTPError as exc:
|
|
186
|
+
raise MailFlatError(f"Network error: {exc}") from exc
|
|
187
|
+
retry_after = _retry_after_seconds(resp)
|
|
188
|
+
if resp.status_code in _RETRY_STATUSES and attempt < self.max_retries:
|
|
189
|
+
attempt += 1
|
|
190
|
+
delay = retry_after if retry_after is not None else min(2 ** attempt * 0.5, _MAX_BACKOFF)
|
|
191
|
+
logger.debug("retrying GET %s after %s (status %s)", path, delay, resp.status_code)
|
|
192
|
+
time.sleep(min(delay, _MAX_BACKOFF))
|
|
193
|
+
continue
|
|
194
|
+
break
|
|
170
195
|
if resp.status_code >= 400:
|
|
171
196
|
detail = ""
|
|
172
197
|
try:
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
"""MailFlat SDK error types — maps HTTP status codes to meaningful Python exceptions.
|
|
2
2
|
|
|
3
|
-
NOT: Bu dosya YAYIN yüzeyi (`packages/`) → docstring'ler İngilizce (CLAUDE.md §3 istisnası).
|
|
4
|
-
Burada docstring bir yorum değil, kullanıcının `help()` ve IDE tooltip'inde gördüğü arayüzdür.
|
|
5
|
-
|
|
6
3
|
Connected to:
|
|
7
4
|
- imported by: mailflat.client, mailflat.inbox, mailflat.__init__
|
|
8
5
|
|
|
@@ -16,6 +13,9 @@ Key exports:
|
|
|
16
13
|
- `OTPTimeoutError` — wait_for_otp / wait_for_message timed out
|
|
17
14
|
- `EncryptedInboxError` — end-to-end encrypted inbox cannot be read through the API
|
|
18
15
|
"""
|
|
16
|
+
# NOT: `packages/` YAYIN yüzeyi → docstring'ler İngilizce (CLAUDE.md §3).
|
|
17
|
+
# Bu satırın kendisi bilerek YORUM: aynı notu docstring'e koymak tam da kaçındığımız
|
|
18
|
+
# şeydi — `help()` docstring'i basar, yorumu basmaz.
|
|
19
19
|
from __future__ import annotations
|
|
20
20
|
|
|
21
21
|
|
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
`Inbox` is returned by the `MailFlat` client and carries the read, wait, send and delete
|
|
4
4
|
methods. `Message` is the typed form of an /api/v1 email payload.
|
|
5
5
|
|
|
6
|
-
NOT: Bu dosya YAYIN yüzeyi (`packages/`) → docstring'ler İngilizce (CLAUDE.md §3 istisnası).
|
|
7
|
-
|
|
8
6
|
Connected to:
|
|
9
7
|
- imports from: mailflat.errors
|
|
10
8
|
- imported by: mailflat.client, mailflat.__init__
|
|
@@ -15,6 +13,9 @@ Key exports:
|
|
|
15
13
|
- `Message` — `.otp`, `.subject`, `.text`, `.html`, `.links`, `.attachments`, `.spam`, ...
|
|
16
14
|
- `Attachment` — `.filename`, `.size_bytes`, `.download()`
|
|
17
15
|
"""
|
|
16
|
+
# NOT: `packages/` YAYIN yüzeyi → docstring'ler İngilizce (CLAUDE.md §3).
|
|
17
|
+
# Bu satırın kendisi bilerek YORUM: aynı notu docstring'e koymak tam da kaçındığımız
|
|
18
|
+
# şeydi — `help()` docstring'i basar, yorumu basmaz.
|
|
18
19
|
from __future__ import annotations
|
|
19
20
|
|
|
20
21
|
import time
|
|
@@ -26,8 +27,8 @@ from .errors import EncryptedInboxError, MailFlatError, OTPTimeoutError
|
|
|
26
27
|
if TYPE_CHECKING: # döngüsel import'tan kaçın (sadece tip ipucu)
|
|
27
28
|
from .client import MailFlat
|
|
28
29
|
|
|
29
|
-
#
|
|
30
|
-
# sorusuna kendi gönderdiği mail cevap olamaz.
|
|
30
|
+
# Yön filtreleri (sunucu sözleşmesi). "in" varsayılan: bir ajan için "mesaj geldi mi"
|
|
31
|
+
# sorusuna kendi gönderdiği mail cevap olamaz. (Yorum — help() çıktısına girmez.)
|
|
31
32
|
DIRECTIONS = ("in", "out", "all")
|
|
32
33
|
|
|
33
34
|
|
|
@@ -41,7 +42,8 @@ def _check_direction(direction: str) -> str:
|
|
|
41
42
|
class Attachment:
|
|
42
43
|
"""Attachment metadata. The bytes are fetched on demand with `.download()`.
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
Contents are never included in a message listing: a single 5 MB attachment would make
|
|
46
|
+
the list unusable, so the bytes live behind a separate request.
|
|
45
47
|
"""
|
|
46
48
|
|
|
47
49
|
id: int | None = None
|
|
@@ -104,8 +106,11 @@ class Message:
|
|
|
104
106
|
# read this instead of parsing the body themselves.
|
|
105
107
|
links: list[str] = field(default_factory=list)
|
|
106
108
|
attachments: list[Attachment] = field(default_factory=list)
|
|
107
|
-
# {score, required, is_spam, rules, scanner}
|
|
108
|
-
# the same as a score of 0.0.
|
|
109
|
+
# {score, required, is_spam, rules, scanner}, or None when the message was NOT SCANNED
|
|
110
|
+
# (not the same as a score of 0.0).
|
|
111
|
+
# NOTE: spam scanning is currently disabled on mailflat.net, so in practice this is
|
|
112
|
+
# None for every message today. The field is wired end to end and will start carrying
|
|
113
|
+
# data as soon as scanning is switched back on.
|
|
109
114
|
spam: dict[str, Any] | None = None
|
|
110
115
|
# Raw message headers (Message-ID lives here — needed for threading and dedup).
|
|
111
116
|
# None on encrypted inboxes: headers carry the Subject, so they go inside the envelope.
|
|
@@ -123,6 +128,26 @@ class Message:
|
|
|
123
128
|
)
|
|
124
129
|
return self._inbox.delete_message(self.id)
|
|
125
130
|
|
|
131
|
+
def header(self, name: str, default: Any = None) -> Any:
|
|
132
|
+
"""Look up a header case-insensitively, e.g. `msg.header("message-id")`.
|
|
133
|
+
|
|
134
|
+
Header names are case-insensitive per RFC 5322, but `headers` is a plain dict whose
|
|
135
|
+
keys come straight off the wire — the real key is `Message-ID`, so
|
|
136
|
+
`headers["Message-Id"]` raises KeyError. Use this instead of indexing.
|
|
137
|
+
"""
|
|
138
|
+
if not self.headers:
|
|
139
|
+
return default
|
|
140
|
+
target = name.replace("_", "-").lower()
|
|
141
|
+
for key, value in self.headers.items():
|
|
142
|
+
if str(key).lower() == target:
|
|
143
|
+
return value
|
|
144
|
+
return default
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def message_id(self) -> str | None:
|
|
148
|
+
"""This message's `Message-ID`, or None when headers were not stored."""
|
|
149
|
+
return self.header("message-id")
|
|
150
|
+
|
|
126
151
|
def mark_read(self) -> dict[str, Any]:
|
|
127
152
|
"""Mark this message as read, so the next poll can skip it."""
|
|
128
153
|
if self._inbox is None or self.id is None:
|
|
@@ -302,14 +327,14 @@ class Inbox:
|
|
|
302
327
|
tracking state on its own.
|
|
303
328
|
"""
|
|
304
329
|
return self._client._post(
|
|
305
|
-
f"/api/v1/inboxes/{self.address}/messages/{message_id}/read")
|
|
330
|
+
f"/api/v1/inboxes/{self.address}/messages/{message_id}/read", idempotent=True)
|
|
306
331
|
|
|
307
332
|
def burn(self) -> dict[str, Any]:
|
|
308
333
|
"""Delete every message in this inbox and keep the address.
|
|
309
334
|
|
|
310
335
|
Useful between test scenarios: the address stays registered wherever you used it.
|
|
311
336
|
"""
|
|
312
|
-
return self._client._post(f"/api/v1/inboxes/{self.address}/burn")
|
|
337
|
+
return self._client._post(f"/api/v1/inboxes/{self.address}/burn", idempotent=True)
|
|
313
338
|
|
|
314
339
|
def delete(self) -> dict[str, Any]:
|
|
315
340
|
"""Delete this inbox and all of its messages. Cannot be undone."""
|
|
@@ -3,17 +3,15 @@
|
|
|
3
3
|
A thin LangChain shell over the `mailflat` SDK; all HTTP and behaviour live in the client.
|
|
4
4
|
`langchain-core` is an optional dependency → `pip install mailflat[langchain]`.
|
|
5
5
|
|
|
6
|
-
NOT: Bu dosya YAYIN yüzeyi (`packages/`) → docstring'ler İngilizce (CLAUDE.md §3 istisnası).
|
|
7
|
-
|
|
8
6
|
Usage:
|
|
9
7
|
from mailflat.langchain import MailFlatToolkit
|
|
10
8
|
toolkit = MailFlatToolkit(api_key=env("MAILFLAT_KEY"))
|
|
11
9
|
tools = toolkit.get_tools() # create_inbox, list_inboxes, read_messages, wait_for_otp,
|
|
12
10
|
# wait_for_message, send_email, mark_read, burn_inbox,
|
|
13
|
-
# delete_inbox (delete_message
|
|
11
|
+
# delete_inbox (delete_message is deliberately absent)
|
|
14
12
|
|
|
15
|
-
⚠️
|
|
16
|
-
(
|
|
13
|
+
⚠️ Tool output goes through `redact_secrets()`: a per-inbox `api_key` must never reach the
|
|
14
|
+
model's context (and from there LangSmith traces) — see B-055.
|
|
17
15
|
|
|
18
16
|
Connected to:
|
|
19
17
|
- imports from: mailflat.client (MailFlat), mailflat.redact, langchain_core.tools
|
|
@@ -22,6 +20,9 @@ Connected to:
|
|
|
22
20
|
Key exports:
|
|
23
21
|
- `MailFlatToolkit(api_key=..., base_url=...)` — `.get_tools()` → list[BaseTool]
|
|
24
22
|
"""
|
|
23
|
+
# NOT: `packages/` YAYIN yüzeyi → docstring'ler İngilizce (CLAUDE.md §3).
|
|
24
|
+
# Bu satırın kendisi bilerek YORUM: aynı notu docstring'e koymak tam da kaçındığımız
|
|
25
|
+
# şeydi — `help()` docstring'i basar, yorumu basmaz.
|
|
25
26
|
from __future__ import annotations
|
|
26
27
|
|
|
27
28
|
from typing import TYPE_CHECKING, Any
|
|
@@ -1,31 +1,32 @@
|
|
|
1
1
|
"""Strips secret fields from agent tool output (B-055).
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
Why it exists: `create_inbox` and `list_inboxes` used to return the backend payload as-is,
|
|
4
|
+
and that payload carried a per-inbox `api_key` (`mf_sk_...`). Tool output goes into the
|
|
5
|
+
MODEL's context, and from there into prompt logs, tracing tools (LangSmith, AI SDK
|
|
6
|
+
telemetry), error reports and the model's own later answers. `list_inboxes` leaked every
|
|
7
|
+
inbox key on the account in a single call.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
- **SDK =
|
|
11
|
-
- **Tool = model
|
|
9
|
+
The split is deliberate:
|
|
10
|
+
- **SDK = code surface.** `Inbox.api_key` stays; code a human wrote may use it.
|
|
11
|
+
- **Tool = model surface.** Everything passing through here is redacted.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
NOT: Bu dosya YAYIN yüzeyi (`packages/`) → docstring'ler İngilizce (CLAUDE.md §3 istisnası).
|
|
13
|
+
Redaction is **name-based, not value-based**: scanning message bodies for strings starting
|
|
14
|
+
with `mf_` would corrupt a legitimate email that happens to discuss a test key. Matching on
|
|
15
|
+
the field name is both exact and harmless.
|
|
18
16
|
|
|
19
17
|
Connected to:
|
|
20
18
|
- imports from: —
|
|
21
|
-
- imported by: mailflat.langchain
|
|
22
|
-
`packages/mcp` (mailflat-mcp,
|
|
23
|
-
|
|
19
|
+
- imported by: mailflat.langchain and this package's tests. Within the repo,
|
|
20
|
+
`packages/mcp` (mailflat-mcp, a separate PyPI package) also imports it — that package
|
|
21
|
+
is not bundled here, so the reference is repo-level.
|
|
24
22
|
|
|
25
23
|
Key exports:
|
|
26
24
|
- `redact_secrets(value)` — copy of the input with secret fields removed
|
|
27
25
|
- `SECRET_NAME_PARTS` — which field names count as secrets
|
|
28
26
|
"""
|
|
27
|
+
# NOT: `packages/` YAYIN yüzeyi → docstring'ler İngilizce (CLAUDE.md §3).
|
|
28
|
+
# Bu satırın kendisi bilerek YORUM: aynı notu docstring'e koymak tam da kaçındığımız
|
|
29
|
+
# şeydi — `help()` docstring'i basar, yorumu basmaz.
|
|
29
30
|
from __future__ import annotations
|
|
30
31
|
|
|
31
32
|
from typing import Any
|
|
File without changes
|
|
File without changes
|
|
File without changes
|