mailkite 0.3.0 → 0.5.0
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.
- checksums.yaml +4 -4
- data/lib/mailkite.rb +112 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 16ccb6cd1fe7bcc80237e945bd42ea0f5f0f69d6fc618b955d8b0e93f70c04ef
|
|
4
|
+
data.tar.gz: 198dd419d07e5dda9fb2023ec653b600098f63f6837f11344db3e156d28c37e3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d61fd1fe911b68d90fb8359eb62fca139a8858d8dd3752d4930420e6ea2fdb6ed59c9264936a3582359ae407d5320946621c7ddcca151e691aeb24a6bf8977a5
|
|
7
|
+
data.tar.gz: 88ff488ba25e4ffccf1fdc4c26b5bcaf38b2a7bf7fb3d795a0f8165c08f53e6166bcb01420ec826c370895b163b83cd427ae7e984c1b97f4953eece037499d69
|
data/lib/mailkite.rb
CHANGED
|
@@ -11,10 +11,11 @@ require "net/http"
|
|
|
11
11
|
require "uri"
|
|
12
12
|
require "json"
|
|
13
13
|
require "openssl"
|
|
14
|
+
require "base64"
|
|
14
15
|
require "cgi"
|
|
15
16
|
|
|
16
17
|
module Mailkite
|
|
17
|
-
VERSION = "0.
|
|
18
|
+
VERSION = "0.5.0"
|
|
18
19
|
DEFAULT_BASE_URL = "https://api.mailkite.dev"
|
|
19
20
|
# Reject webhook events older than this (ms) to block replays. Pass 0 to disable.
|
|
20
21
|
DEFAULT_TOLERANCE_MS = 5 * 60 * 1000
|
|
@@ -54,6 +55,83 @@ module Mailkite
|
|
|
54
55
|
diff.zero?
|
|
55
56
|
end
|
|
56
57
|
|
|
58
|
+
# The canonical body to return from a webhook handler so MailKite marks the
|
|
59
|
+
# delivery acknowledged. Returns the exact string `{"status":"ok"}`.
|
|
60
|
+
def self.reply_ok
|
|
61
|
+
'{"status":"ok"}'
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Control-mode reply telling MailKite to mark the message as spam.
|
|
65
|
+
# Returns the exact string `{"status":"spam"}`.
|
|
66
|
+
def self.reply_spam
|
|
67
|
+
'{"status":"spam"}'
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Control-mode reply telling MailKite to drop (discard) the message.
|
|
71
|
+
# Returns the exact string `{"status":"drop"}`.
|
|
72
|
+
def self.reply_drop
|
|
73
|
+
'{"status":"drop"}'
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Control-mode reply telling MailKite to block the sender.
|
|
77
|
+
# Returns the exact string `{"status":"ok","actions":[{"type":"block-sender"}]}`.
|
|
78
|
+
def self.reply_block_sender
|
|
79
|
+
'{"status":"ok","actions":[{"type":"block-sender"}]}'
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Encrypt a UTF-8 string to a customer RSA public key (SPKI/PEM), producing the
|
|
83
|
+
# MailKite at-rest envelope as a compact JSON string. Hybrid scheme: a fresh
|
|
84
|
+
# AES-256-GCM content key encrypts the data, then the raw key is wrapped with
|
|
85
|
+
# RSA-OAEP (SHA-256). Local only — no network.
|
|
86
|
+
def self.encrypt(plaintext, public_key)
|
|
87
|
+
pub = OpenSSL::PKey.read(public_key)
|
|
88
|
+
fp = OpenSSL::Digest::SHA256.hexdigest(pub.to_der)
|
|
89
|
+
|
|
90
|
+
raw_key = OpenSSL::Random.random_bytes(32)
|
|
91
|
+
iv = OpenSSL::Random.random_bytes(12)
|
|
92
|
+
|
|
93
|
+
cipher = OpenSSL::Cipher.new("aes-256-gcm")
|
|
94
|
+
cipher.encrypt
|
|
95
|
+
cipher.key = raw_key
|
|
96
|
+
cipher.iv = iv
|
|
97
|
+
body = cipher.update(plaintext.to_s.dup.force_encoding("UTF-8")) + cipher.final
|
|
98
|
+
ciphertext = body + cipher.auth_tag # GCM ct with 16-byte tag appended
|
|
99
|
+
|
|
100
|
+
wrapped = pub.encrypt(raw_key, rsa_padding_mode: "oaep", rsa_oaep_md: "sha256", rsa_mgf1_md: "sha256")
|
|
101
|
+
|
|
102
|
+
JSON.generate({
|
|
103
|
+
"v" => 1,
|
|
104
|
+
"keyAlg" => "RSA-OAEP-256",
|
|
105
|
+
"fp" => fp,
|
|
106
|
+
"enc" => "A256GCM",
|
|
107
|
+
"iv" => Base64.strict_encode64(iv),
|
|
108
|
+
"wrappedKey" => Base64.strict_encode64(wrapped),
|
|
109
|
+
"ciphertext" => Base64.strict_encode64(ciphertext),
|
|
110
|
+
})
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Decrypt a MailKite at-rest envelope (JSON string) with the matching RSA
|
|
114
|
+
# private key (PEM), returning the original UTF-8 plaintext. Local only.
|
|
115
|
+
def self.decrypt(envelope, private_key)
|
|
116
|
+
env = envelope.is_a?(String) ? JSON.parse(envelope) : envelope
|
|
117
|
+
priv = OpenSSL::PKey.read(private_key)
|
|
118
|
+
|
|
119
|
+
iv = Base64.strict_decode64(env["iv"])
|
|
120
|
+
wrapped = Base64.strict_decode64(env["wrappedKey"])
|
|
121
|
+
ct = Base64.strict_decode64(env["ciphertext"])
|
|
122
|
+
body = ct[0...-16]
|
|
123
|
+
tag = ct[-16..]
|
|
124
|
+
|
|
125
|
+
raw_key = priv.decrypt(wrapped, rsa_padding_mode: "oaep", rsa_oaep_md: "sha256", rsa_mgf1_md: "sha256")
|
|
126
|
+
|
|
127
|
+
cipher = OpenSSL::Cipher.new("aes-256-gcm")
|
|
128
|
+
cipher.decrypt
|
|
129
|
+
cipher.key = raw_key
|
|
130
|
+
cipher.iv = iv
|
|
131
|
+
cipher.auth_tag = tag
|
|
132
|
+
(cipher.update(body) + cipher.final).force_encoding("UTF-8")
|
|
133
|
+
end
|
|
134
|
+
|
|
57
135
|
class Error < StandardError
|
|
58
136
|
attr_reader :status, :body
|
|
59
137
|
|
|
@@ -106,6 +184,14 @@ module Mailkite
|
|
|
106
184
|
request("POST", "/v1/send", message)
|
|
107
185
|
end
|
|
108
186
|
|
|
187
|
+
# Upload a file (base64 `content`) and get back a secure, time-limited URL to
|
|
188
|
+
# reference as a send() attachment ({ filename, url }) or link inline —
|
|
189
|
+
# instead of base64-inlining large files on every send. `file` keys:
|
|
190
|
+
# filename, content (base64), plus optional contentType and retentionDays.
|
|
191
|
+
def uploadAttachment(file)
|
|
192
|
+
request("POST", "/v1/attachments", file)
|
|
193
|
+
end
|
|
194
|
+
|
|
109
195
|
# Run an inbound message through an AI agent. message keys: text (required),
|
|
110
196
|
# plus optional subject/from/html/routeId/address/model.
|
|
111
197
|
def agent(message)
|
|
@@ -204,5 +290,30 @@ module Mailkite
|
|
|
204
290
|
def verifyWebhook(signature, payload, secret, tolerance_ms = DEFAULT_TOLERANCE_MS)
|
|
205
291
|
Mailkite.verify_webhook(signature, payload, secret, tolerance_ms)
|
|
206
292
|
end
|
|
293
|
+
|
|
294
|
+
# Instance wrappers around the module-level crypto helpers. No network call.
|
|
295
|
+
def reply_ok
|
|
296
|
+
Mailkite.reply_ok
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def reply_spam
|
|
300
|
+
Mailkite.reply_spam
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def reply_drop
|
|
304
|
+
Mailkite.reply_drop
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def reply_block_sender
|
|
308
|
+
Mailkite.reply_block_sender
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def encrypt(plaintext, public_key)
|
|
312
|
+
Mailkite.encrypt(plaintext, public_key)
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def decrypt(envelope, private_key)
|
|
316
|
+
Mailkite.decrypt(envelope, private_key)
|
|
317
|
+
end
|
|
207
318
|
end
|
|
208
319
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mailkite
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- MailKite
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-06-
|
|
11
|
+
date: 2026-06-25 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Send and manage email over your own authenticated domain with MailKite.
|
|
14
14
|
email:
|