mailkite-dev 0.3.0__tar.gz → 0.5.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.3.0 → mailkite_dev-0.5.0}/PKG-INFO +2 -1
- {mailkite_dev-0.3.0 → mailkite_dev-0.5.0}/mailkite/__init__.py +139 -1
- {mailkite_dev-0.3.0 → mailkite_dev-0.5.0}/mailkite_dev.egg-info/PKG-INFO +2 -1
- {mailkite_dev-0.3.0 → mailkite_dev-0.5.0}/mailkite_dev.egg-info/SOURCES.txt +1 -0
- mailkite_dev-0.5.0/mailkite_dev.egg-info/requires.txt +1 -0
- {mailkite_dev-0.3.0 → mailkite_dev-0.5.0}/pyproject.toml +4 -1
- {mailkite_dev-0.3.0 → mailkite_dev-0.5.0}/README.md +0 -0
- {mailkite_dev-0.3.0 → mailkite_dev-0.5.0}/mailkite_dev.egg-info/dependency_links.txt +0 -0
- {mailkite_dev-0.3.0 → mailkite_dev-0.5.0}/mailkite_dev.egg-info/top_level.txt +0 -0
- {mailkite_dev-0.3.0 → mailkite_dev-0.5.0}/setup.cfg +0 -0
- {mailkite_dev-0.3.0 → mailkite_dev-0.5.0}/tests/test_sdk.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mailkite-dev
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.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
|
|
@@ -8,6 +8,7 @@ Project-URL: Repository, https://github.com/mailkite/mailkite
|
|
|
8
8
|
Keywords: mailkite,email,transactional,api
|
|
9
9
|
Requires-Python: >=3.7
|
|
10
10
|
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: cryptography>=41
|
|
11
12
|
|
|
12
13
|
# MailKite for Python
|
|
13
14
|
|
|
@@ -8,9 +8,11 @@ method per API endpoint. Zero dependencies — uses the standard library.
|
|
|
8
8
|
res = mk.send({"from": ..., "to": ..., "subject": ..., "text": ...})
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
+
import base64
|
|
11
12
|
import hashlib
|
|
12
13
|
import hmac
|
|
13
14
|
import json
|
|
15
|
+
import os
|
|
14
16
|
import time
|
|
15
17
|
import urllib.error
|
|
16
18
|
import urllib.parse
|
|
@@ -20,7 +22,7 @@ DEFAULT_BASE_URL = "https://api.mailkite.dev"
|
|
|
20
22
|
# Reject webhook events older than this (ms) to block replays. Pass 0 to disable.
|
|
21
23
|
DEFAULT_TOLERANCE_MS = 5 * 60 * 1000
|
|
22
24
|
|
|
23
|
-
__all__ = ["MailKite", "MailKiteError", "verify_webhook"]
|
|
25
|
+
__all__ = ["MailKite", "MailKiteError", "verify_webhook", "reply_ok", "reply_spam", "reply_drop", "reply_block_sender", "encrypt", "decrypt"]
|
|
24
26
|
|
|
25
27
|
|
|
26
28
|
def verify_webhook(signature, payload, secret, toleranceMs=DEFAULT_TOLERANCE_MS):
|
|
@@ -51,6 +53,103 @@ def verify_webhook(signature, payload, secret, toleranceMs=DEFAULT_TOLERANCE_MS)
|
|
|
51
53
|
return hmac.compare_digest(expected, v1)
|
|
52
54
|
|
|
53
55
|
|
|
56
|
+
def reply_ok():
|
|
57
|
+
"""Return the canonical ``200 OK`` webhook acknowledgement body.
|
|
58
|
+
|
|
59
|
+
Inbound webhook handlers should reply with this exact string so MailKite
|
|
60
|
+
marks the delivery as accepted. No network call."""
|
|
61
|
+
return '{"status":"ok"}'
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def reply_spam():
|
|
65
|
+
"""Control-mode reply telling MailKite to mark the message as spam.
|
|
66
|
+
|
|
67
|
+
Returns the exact string ``{"status":"spam"}``. No network call."""
|
|
68
|
+
return '{"status":"spam"}'
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def reply_drop():
|
|
72
|
+
"""Control-mode reply telling MailKite to drop (discard) the message.
|
|
73
|
+
|
|
74
|
+
Returns the exact string ``{"status":"drop"}``. No network call."""
|
|
75
|
+
return '{"status":"drop"}'
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def reply_block_sender():
|
|
79
|
+
"""Control-mode reply telling MailKite to block the sender.
|
|
80
|
+
|
|
81
|
+
Returns the exact string ``{"status":"ok","actions":[{"type":"block-sender"}]}``.
|
|
82
|
+
No network call."""
|
|
83
|
+
return '{"status":"ok","actions":[{"type":"block-sender"}]}'
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def encrypt(plaintext, public_key):
|
|
87
|
+
"""Encrypt a UTF-8 string to a MailKite at-rest envelope (JSON string).
|
|
88
|
+
|
|
89
|
+
Hybrid encryption matching MailKite's at-rest scheme: a fresh AES-256-GCM
|
|
90
|
+
content key encrypts the plaintext, then that key is wrapped with the
|
|
91
|
+
customer's RSA-OAEP (SHA-256) ``public_key`` (SPKI/PEM). Only the holder of
|
|
92
|
+
the matching private key can :func:`decrypt`. No network call. Requires the
|
|
93
|
+
``cryptography`` package."""
|
|
94
|
+
from cryptography.hazmat.primitives import hashes, serialization
|
|
95
|
+
from cryptography.hazmat.primitives.asymmetric import padding
|
|
96
|
+
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
97
|
+
|
|
98
|
+
pem = public_key.encode("utf-8") if isinstance(public_key, str) else public_key
|
|
99
|
+
pub = serialization.load_pem_public_key(pem)
|
|
100
|
+
spki_der = pub.public_bytes(
|
|
101
|
+
encoding=serialization.Encoding.DER,
|
|
102
|
+
format=serialization.PublicFormat.SubjectPublicKeyInfo,
|
|
103
|
+
)
|
|
104
|
+
fp = hashlib.sha256(spki_der).hexdigest()
|
|
105
|
+
|
|
106
|
+
content_key = AESGCM.generate_key(bit_length=256)
|
|
107
|
+
iv = os.urandom(12)
|
|
108
|
+
# AESGCM.encrypt returns ciphertext || 16-byte tag (matches WebCrypto).
|
|
109
|
+
ciphertext = AESGCM(content_key).encrypt(iv, plaintext.encode("utf-8"), None)
|
|
110
|
+
wrapped = pub.encrypt(
|
|
111
|
+
content_key,
|
|
112
|
+
padding.OAEP(mgf=padding.MGF1(hashes.SHA256()), algorithm=hashes.SHA256(), label=None),
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
def b64(b):
|
|
116
|
+
return base64.b64encode(b).decode("ascii")
|
|
117
|
+
|
|
118
|
+
return json.dumps({
|
|
119
|
+
"v": 1,
|
|
120
|
+
"keyAlg": "RSA-OAEP-256",
|
|
121
|
+
"fp": fp,
|
|
122
|
+
"enc": "A256GCM",
|
|
123
|
+
"iv": b64(iv),
|
|
124
|
+
"wrappedKey": b64(wrapped),
|
|
125
|
+
"ciphertext": b64(ciphertext),
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def decrypt(envelope, private_key):
|
|
130
|
+
"""Decrypt a MailKite at-rest ``envelope`` (JSON string) back to plaintext.
|
|
131
|
+
|
|
132
|
+
Inverse of :func:`encrypt`: unwraps the AES-256-GCM content key with the
|
|
133
|
+
RSA-OAEP (SHA-256) ``private_key`` (PKCS8/PEM), then decrypts the ciphertext
|
|
134
|
+
(which carries its 16-byte GCM tag) and returns the UTF-8 string. No network
|
|
135
|
+
call. Requires the ``cryptography`` package."""
|
|
136
|
+
from cryptography.hazmat.primitives import hashes, serialization
|
|
137
|
+
from cryptography.hazmat.primitives.asymmetric import padding
|
|
138
|
+
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
139
|
+
|
|
140
|
+
env = json.loads(envelope)
|
|
141
|
+
pem = private_key.encode("utf-8") if isinstance(private_key, str) else private_key
|
|
142
|
+
priv = serialization.load_pem_private_key(pem, password=None)
|
|
143
|
+
content_key = priv.decrypt(
|
|
144
|
+
base64.b64decode(env["wrappedKey"]),
|
|
145
|
+
padding.OAEP(mgf=padding.MGF1(hashes.SHA256()), algorithm=hashes.SHA256(), label=None),
|
|
146
|
+
)
|
|
147
|
+
plaintext = AESGCM(content_key).decrypt(
|
|
148
|
+
base64.b64decode(env["iv"]), base64.b64decode(env["ciphertext"]), None
|
|
149
|
+
)
|
|
150
|
+
return plaintext.decode("utf-8")
|
|
151
|
+
|
|
152
|
+
|
|
54
153
|
class MailKiteError(Exception):
|
|
55
154
|
def __init__(self, status, message, body=None):
|
|
56
155
|
super().__init__(message)
|
|
@@ -90,6 +189,15 @@ class MailKite:
|
|
|
90
189
|
and ``templateData`` (dict) to supply its variables."""
|
|
91
190
|
return self.request("POST", "/v1/send", message)
|
|
92
191
|
|
|
192
|
+
def uploadAttachment(self, file):
|
|
193
|
+
"""Upload a file (base64 ``content``) and get back a secure,
|
|
194
|
+
time-limited URL to reference as a send() attachment
|
|
195
|
+
(``{ filename, url }``) or link inline — instead of base64-inlining
|
|
196
|
+
large files on every send. ``file`` is a dict with ``filename`` and
|
|
197
|
+
``content`` (base64 str), plus optional ``contentType`` and
|
|
198
|
+
``retentionDays`` (7 | 30 | 90 | 365, default 7)."""
|
|
199
|
+
return self.request("POST", "/v1/attachments", file)
|
|
200
|
+
|
|
93
201
|
def agent(self, message):
|
|
94
202
|
"""Send a message to an AI agent inbox. ``message`` is a dict with
|
|
95
203
|
``text`` (required) and optional ``subject``, ``from``, ``html``,
|
|
@@ -168,3 +276,33 @@ class MailKite:
|
|
|
168
276
|
:func:`verify_webhook` — this is a thin instance wrapper, so you can
|
|
169
277
|
call it on an existing client without re-importing."""
|
|
170
278
|
return verify_webhook(signature, payload, secret, toleranceMs)
|
|
279
|
+
|
|
280
|
+
def reply_ok(self):
|
|
281
|
+
"""Canonical webhook acknowledgement body. See module-level
|
|
282
|
+
:func:`reply_ok`."""
|
|
283
|
+
return reply_ok()
|
|
284
|
+
|
|
285
|
+
def reply_spam(self):
|
|
286
|
+
"""Control-mode reply marking the message as spam. See module-level
|
|
287
|
+
:func:`reply_spam`."""
|
|
288
|
+
return reply_spam()
|
|
289
|
+
|
|
290
|
+
def reply_drop(self):
|
|
291
|
+
"""Control-mode reply dropping the message. See module-level
|
|
292
|
+
:func:`reply_drop`."""
|
|
293
|
+
return reply_drop()
|
|
294
|
+
|
|
295
|
+
def reply_block_sender(self):
|
|
296
|
+
"""Control-mode reply blocking the sender. See module-level
|
|
297
|
+
:func:`reply_block_sender`."""
|
|
298
|
+
return reply_block_sender()
|
|
299
|
+
|
|
300
|
+
def encrypt(self, plaintext, public_key):
|
|
301
|
+
"""Encrypt to a MailKite at-rest envelope. See module-level
|
|
302
|
+
:func:`encrypt`."""
|
|
303
|
+
return encrypt(plaintext, public_key)
|
|
304
|
+
|
|
305
|
+
def decrypt(self, envelope, private_key):
|
|
306
|
+
"""Decrypt a MailKite at-rest envelope. See module-level
|
|
307
|
+
:func:`decrypt`."""
|
|
308
|
+
return decrypt(envelope, private_key)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mailkite-dev
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.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
|
|
@@ -8,6 +8,7 @@ Project-URL: Repository, https://github.com/mailkite/mailkite
|
|
|
8
8
|
Keywords: mailkite,email,transactional,api
|
|
9
9
|
Requires-Python: >=3.7
|
|
10
10
|
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: cryptography>=41
|
|
11
12
|
|
|
12
13
|
# MailKite for Python
|
|
13
14
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cryptography>=41
|
|
@@ -7,12 +7,15 @@ 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.5.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"
|
|
14
14
|
license = { text = "MIT" }
|
|
15
15
|
keywords = ["mailkite", "email", "transactional", "api"]
|
|
16
|
+
# `cryptography` powers the local at-rest encrypt()/decrypt() helpers; everything
|
|
17
|
+
# else in the SDK is standard-library only.
|
|
18
|
+
dependencies = ["cryptography>=41"]
|
|
16
19
|
|
|
17
20
|
[project.urls]
|
|
18
21
|
Homepage = "https://mailkite.dev/docs/libraries"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|