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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mailkite-dev
3
- Version: 0.4.0
3
+ Version: 0.6.0
4
4
  Summary: Official MailKite SDK for Python — send and manage email over your own authenticated domain.
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://mailkite.dev/docs/libraries
@@ -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
- __all__ = ["MailKite", "MailKiteError", "verify_webhook", "reply_ok", "encrypt", "decrypt"]
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`."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mailkite-dev
3
- Version: 0.4.0
3
+ Version: 0.6.0
4
4
  Summary: Official MailKite SDK for Python — send and manage email over your own authenticated domain.
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://mailkite.dev/docs/libraries
@@ -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.4.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