mailkite-dev 0.4.0__tar.gz → 0.6.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.
- {mailkite_dev-0.4.0 → mailkite_dev-0.6.0}/PKG-INFO +1 -1
- {mailkite_dev-0.4.0 → mailkite_dev-0.6.0}/mailkite/__init__.py +144 -1
- {mailkite_dev-0.4.0 → mailkite_dev-0.6.0}/mailkite_dev.egg-info/PKG-INFO +1 -1
- {mailkite_dev-0.4.0 → mailkite_dev-0.6.0}/pyproject.toml +1 -1
- {mailkite_dev-0.4.0 → mailkite_dev-0.6.0}/README.md +0 -0
- {mailkite_dev-0.4.0 → mailkite_dev-0.6.0}/mailkite_dev.egg-info/SOURCES.txt +0 -0
- {mailkite_dev-0.4.0 → mailkite_dev-0.6.0}/mailkite_dev.egg-info/dependency_links.txt +0 -0
- {mailkite_dev-0.4.0 → mailkite_dev-0.6.0}/mailkite_dev.egg-info/requires.txt +0 -0
- {mailkite_dev-0.4.0 → mailkite_dev-0.6.0}/mailkite_dev.egg-info/top_level.txt +0 -0
- {mailkite_dev-0.4.0 → mailkite_dev-0.6.0}/setup.cfg +0 -0
- {mailkite_dev-0.4.0 → mailkite_dev-0.6.0}/tests/test_sdk.py +0 -0
|
@@ -22,7 +22,37 @@ DEFAULT_BASE_URL = "https://api.mailkite.dev"
|
|
|
22
22
|
# Reject webhook events older than this (ms) to block replays. Pass 0 to disable.
|
|
23
23
|
DEFAULT_TOLERANCE_MS = 5 * 60 * 1000
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
# Best-effort extension -> MIME map for raw binary attachment uploads.
|
|
26
|
+
_CONTENT_TYPES = {
|
|
27
|
+
"pdf": "application/pdf",
|
|
28
|
+
"png": "image/png",
|
|
29
|
+
"jpg": "image/jpeg",
|
|
30
|
+
"jpeg": "image/jpeg",
|
|
31
|
+
"gif": "image/gif",
|
|
32
|
+
"webp": "image/webp",
|
|
33
|
+
"svg": "image/svg+xml",
|
|
34
|
+
"csv": "text/csv",
|
|
35
|
+
"txt": "text/plain",
|
|
36
|
+
"html": "text/html",
|
|
37
|
+
"json": "application/json",
|
|
38
|
+
"zip": "application/zip",
|
|
39
|
+
"doc": "application/msword",
|
|
40
|
+
"docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
41
|
+
"xls": "application/vnd.ms-excel",
|
|
42
|
+
"xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
43
|
+
"ics": "text/calendar",
|
|
44
|
+
"ical": "text/calendar",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _guess_content_type(name):
|
|
49
|
+
"""Guess a MIME type from a filename's extension (default octet-stream)."""
|
|
50
|
+
ext = ""
|
|
51
|
+
if name and "." in name:
|
|
52
|
+
ext = name.rsplit(".", 1)[1].lower()
|
|
53
|
+
return _CONTENT_TYPES.get(ext, "application/octet-stream")
|
|
54
|
+
|
|
55
|
+
__all__ = ["MailKite", "MailKiteError", "verify_webhook", "reply_ok", "reply_spam", "reply_drop", "reply_block_sender", "encrypt", "decrypt"]
|
|
26
56
|
|
|
27
57
|
|
|
28
58
|
def verify_webhook(signature, payload, secret, toleranceMs=DEFAULT_TOLERANCE_MS):
|
|
@@ -61,6 +91,28 @@ def reply_ok():
|
|
|
61
91
|
return '{"status":"ok"}'
|
|
62
92
|
|
|
63
93
|
|
|
94
|
+
def reply_spam():
|
|
95
|
+
"""Control-mode reply telling MailKite to mark the message as spam.
|
|
96
|
+
|
|
97
|
+
Returns the exact string ``{"status":"spam"}``. No network call."""
|
|
98
|
+
return '{"status":"spam"}'
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def reply_drop():
|
|
102
|
+
"""Control-mode reply telling MailKite to drop (discard) the message.
|
|
103
|
+
|
|
104
|
+
Returns the exact string ``{"status":"drop"}``. No network call."""
|
|
105
|
+
return '{"status":"drop"}'
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def reply_block_sender():
|
|
109
|
+
"""Control-mode reply telling MailKite to block the sender.
|
|
110
|
+
|
|
111
|
+
Returns the exact string ``{"status":"ok","actions":[{"type":"block-sender"}]}``.
|
|
112
|
+
No network call."""
|
|
113
|
+
return '{"status":"ok","actions":[{"type":"block-sender"}]}'
|
|
114
|
+
|
|
115
|
+
|
|
64
116
|
def encrypt(plaintext, public_key):
|
|
65
117
|
"""Encrypt a UTF-8 string to a MailKite at-rest envelope (JSON string).
|
|
66
118
|
|
|
@@ -159,6 +211,28 @@ class MailKite:
|
|
|
159
211
|
message = parsed.get("error") if isinstance(parsed, dict) else None
|
|
160
212
|
raise MailKiteError(e.code, message or e.reason or "HTTP %d" % e.code, parsed)
|
|
161
213
|
|
|
214
|
+
# Raw-binary variant of request(): the body is the file bytes themselves
|
|
215
|
+
# (not JSON, not multipart) and filename/retentionDays ride in the query.
|
|
216
|
+
def requestBinary(self, method, path, data, filename, contentType=None, retentionDays=None):
|
|
217
|
+
query = {"filename": filename}
|
|
218
|
+
if retentionDays is not None:
|
|
219
|
+
query["retentionDays"] = retentionDays
|
|
220
|
+
url = self.baseUrl + path + "?" + urllib.parse.urlencode(query)
|
|
221
|
+
headers = {
|
|
222
|
+
"Authorization": "Bearer " + self.apiKey,
|
|
223
|
+
"Content-Type": contentType or "application/octet-stream",
|
|
224
|
+
}
|
|
225
|
+
req = urllib.request.Request(url, data=data, method=method, headers=headers)
|
|
226
|
+
try:
|
|
227
|
+
with urllib.request.urlopen(req) as resp:
|
|
228
|
+
text = resp.read().decode("utf-8")
|
|
229
|
+
return json.loads(text) if text else None
|
|
230
|
+
except urllib.error.HTTPError as e:
|
|
231
|
+
text = e.read().decode("utf-8")
|
|
232
|
+
parsed = json.loads(text) if text else None
|
|
233
|
+
message = parsed.get("error") if isinstance(parsed, dict) else None
|
|
234
|
+
raise MailKiteError(e.code, message or e.reason or "HTTP %d" % e.code, parsed)
|
|
235
|
+
|
|
162
236
|
# --- Sending ----------------------------------------------------------
|
|
163
237
|
def send(self, message):
|
|
164
238
|
"""Send an email. ``message`` is a dict with ``from``, ``to`` and a
|
|
@@ -167,6 +241,60 @@ class MailKite:
|
|
|
167
241
|
and ``templateData`` (dict) to supply its variables."""
|
|
168
242
|
return self.request("POST", "/v1/send", message)
|
|
169
243
|
|
|
244
|
+
def uploadAttachment(self, file):
|
|
245
|
+
"""Upload a file and get back a secure, time-limited URL to reference as
|
|
246
|
+
a send() attachment (``{ filename, url }``) or link inline — instead of
|
|
247
|
+
base64-inlining large files on every send. ``file`` is a dict that
|
|
248
|
+
provides the file ONE of four ways (checked in this order):
|
|
249
|
+
|
|
250
|
+
- ``url`` (str): MailKite fetches and re-hosts the remote file.
|
|
251
|
+
- ``bytes`` (raw ``bytes``): uploaded directly as a raw binary body.
|
|
252
|
+
- ``path`` (str): a local file read off disk, then uploaded as raw bytes;
|
|
253
|
+
``filename`` and ``contentType`` are derived from the path if omitted.
|
|
254
|
+
- ``content`` (str): a base64-encoded body (the original JSON form).
|
|
255
|
+
|
|
256
|
+
Optional in every mode: ``filename``, ``contentType`` and
|
|
257
|
+
``retentionDays`` (7 | 30 | 90 | 365, default 7). Returns
|
|
258
|
+
``{id, url, filename, contentType, size, expiresAt}``."""
|
|
259
|
+
file = file or {}
|
|
260
|
+
filename = file.get("filename")
|
|
261
|
+
content = file.get("content")
|
|
262
|
+
url = file.get("url")
|
|
263
|
+
path = file.get("path")
|
|
264
|
+
data = file.get("bytes")
|
|
265
|
+
contentType = file.get("contentType")
|
|
266
|
+
retentionDays = file.get("retentionDays")
|
|
267
|
+
if url is not None:
|
|
268
|
+
body = {"url": url}
|
|
269
|
+
if filename is not None:
|
|
270
|
+
body["filename"] = filename
|
|
271
|
+
if contentType is not None:
|
|
272
|
+
body["contentType"] = contentType
|
|
273
|
+
if retentionDays is not None:
|
|
274
|
+
body["retentionDays"] = retentionDays
|
|
275
|
+
return self.request("POST", "/v1/attachments", body)
|
|
276
|
+
if data is not None or path is not None:
|
|
277
|
+
name = filename
|
|
278
|
+
ctype = contentType
|
|
279
|
+
if data is None:
|
|
280
|
+
with open(path, "rb") as f:
|
|
281
|
+
data = f.read()
|
|
282
|
+
if not name:
|
|
283
|
+
name = os.path.basename(path)
|
|
284
|
+
if not ctype:
|
|
285
|
+
ctype = _guess_content_type(name)
|
|
286
|
+
return self.requestBinary("POST", "/v1/attachments", data, name, ctype, retentionDays)
|
|
287
|
+
if content is not None:
|
|
288
|
+
body = {"content": content}
|
|
289
|
+
if filename is not None:
|
|
290
|
+
body["filename"] = filename
|
|
291
|
+
if contentType is not None:
|
|
292
|
+
body["contentType"] = contentType
|
|
293
|
+
if retentionDays is not None:
|
|
294
|
+
body["retentionDays"] = retentionDays
|
|
295
|
+
return self.request("POST", "/v1/attachments", body)
|
|
296
|
+
raise MailKiteError(0, "uploadAttachment needs one of: path, bytes, url, or base64 content")
|
|
297
|
+
|
|
170
298
|
def agent(self, message):
|
|
171
299
|
"""Send a message to an AI agent inbox. ``message`` is a dict with
|
|
172
300
|
``text`` (required) and optional ``subject``, ``from``, ``html``,
|
|
@@ -251,6 +379,21 @@ class MailKite:
|
|
|
251
379
|
:func:`reply_ok`."""
|
|
252
380
|
return reply_ok()
|
|
253
381
|
|
|
382
|
+
def reply_spam(self):
|
|
383
|
+
"""Control-mode reply marking the message as spam. See module-level
|
|
384
|
+
:func:`reply_spam`."""
|
|
385
|
+
return reply_spam()
|
|
386
|
+
|
|
387
|
+
def reply_drop(self):
|
|
388
|
+
"""Control-mode reply dropping the message. See module-level
|
|
389
|
+
:func:`reply_drop`."""
|
|
390
|
+
return reply_drop()
|
|
391
|
+
|
|
392
|
+
def reply_block_sender(self):
|
|
393
|
+
"""Control-mode reply blocking the sender. See module-level
|
|
394
|
+
:func:`reply_block_sender`."""
|
|
395
|
+
return reply_block_sender()
|
|
396
|
+
|
|
254
397
|
def encrypt(self, plaintext, public_key):
|
|
255
398
|
"""Encrypt to a MailKite at-rest envelope. See module-level
|
|
256
399
|
:func:`encrypt`."""
|
|
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
|
|
|
7
7
|
# old account. Installs as `pip install mailkite-dev`; still imports as `import mailkite`
|
|
8
8
|
# (the package dir below is unchanged). Rename back to `mailkite` once reclaimed.
|
|
9
9
|
name = "mailkite-dev"
|
|
10
|
-
version = "0.
|
|
10
|
+
version = "0.6.0"
|
|
11
11
|
description = "Official MailKite SDK for Python — send and manage email over your own authenticated domain."
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
requires-python = ">=3.7"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|